581 lines
27 KiB
Markdown
581 lines
27 KiB
Markdown
# Printserver
|
||
|
||
A containerized CUPS print server that shares a **Brother HL-4150CDN** network
|
||
printer over IPP and advertises it via AirPrint (mDNS/DNS-SD). It prints by
|
||
sending **PostScript (BR-Script3)** straight to the printer, so no vendor driver
|
||
and no 32-bit libraries run in the container.
|
||
|
||
The container bundles:
|
||
|
||
- CUPS (IPP + web admin on port 631) with cups-filters for format conversion
|
||
- Avahi / DNS-SD for AirPrint discovery (UDP 5353)
|
||
- Brother's official HL-4150CDN **BR-Script3 PPD** for device options
|
||
|
||
## How it works
|
||
|
||
The HL-4150CDN emulates PCL6 and BR-Script3 (PostScript 3), and accepts
|
||
PostScript over raw AppSocket (port 9100). CUPS converts whatever a client
|
||
sends into PostScript and the BR-Script3 PPD turns the selected queue options
|
||
into PostScript `setpagedevice` instructions:
|
||
|
||
```
|
||
macOS / iOS / Windows / Linux container printer
|
||
┌─────────────────┐ IPP/AirPrint ┌──────────────────────────┐ raw ┌────────────┐
|
||
│ AirPrint (URF) │ ───────────────▶ │ CUPS │ ──────▶ │ HL-4150CDN │
|
||
│ PDF / PostScript│ │ URF/PWG/PDF → PostScript│ :9100 │ BR-Script3 │
|
||
└─────────────────┘ │ pstops → setpagedevice │ └────────────┘
|
||
└──────────────────────────┘
|
||
```
|
||
|
||
For example, `Duplex=DuplexNoTumble BRPrintQuality=Color InputSlot=Tray2`
|
||
becomes:
|
||
|
||
```postscript
|
||
<</Duplex true /Tumble false>>setpagedevice
|
||
<</BRProcessColor 4 /BRHrc 0>>setpagedevice
|
||
<</ManualFeed false /BRTraysw false /BRFeeder 1>> setpagedevice
|
||
```
|
||
|
||
## Layout
|
||
|
||
| Path | Purpose |
|
||
| --- | --- |
|
||
| `Containerfile` | Image definition (Debian base, CUPS + cups-filters + Avahi, BR-Script3 PPD) |
|
||
| `cupsd.conf` | Container CUPS configuration (listen on all interfaces, share printers) |
|
||
| `entrypoint.sh` | Starts D-Bus, Avahi and CUPS, generates the `_ipps` certificate, then registers the printer queue |
|
||
| `assets/Brother HL-4150CDN/ppd/` | Official BR-Script3 PPD used for the queue |
|
||
| `assets/Brother HL-4150CDN/Windows/` | Vendor Windows driver the PPD was extracted from |
|
||
|
||
## Prerequisites
|
||
|
||
- Podman (or a compatible builder such as Docker/Buildah)
|
||
- The printer reachable on the network, with a known IP address or hostname
|
||
|
||
## Build
|
||
|
||
Run from the repository root:
|
||
|
||
```sh
|
||
podman build -t printserver:latest .
|
||
```
|
||
|
||
The image is architecture-independent (no i386 or other 32-bit binaries), so it
|
||
builds natively on x86_64 and arm64 hosts alike.
|
||
|
||
## Run
|
||
|
||
mDNS does not work through a normal container network, so use **host
|
||
networking**:
|
||
|
||
```sh
|
||
podman run -d --name printserver \
|
||
--network host \
|
||
-e PRINTER_IP=192.168.100.7 \
|
||
-e PRINTER_NAME=HL-4150CDN \
|
||
printserver:latest
|
||
```
|
||
|
||
### Environment variables
|
||
|
||
| Variable | Default | Description |
|
||
| --- | --- | --- |
|
||
| `PRINTER_IP` | *(empty)* | IP address / host name of the network printer. A device URI is derived from it (see `PRINTER_PROTOCOL`). |
|
||
| `PRINTER_URI` | *(empty)* | Full device URI. Overrides `PRINTER_IP` when set. |
|
||
| `PRINTER_PROTOCOL` | `socket` | Protocol used with `PRINTER_IP`: `socket`, `lpd` or `ipp`. |
|
||
| `PRINTER_NAME` | `HL4150CDN` | CUPS queue name. Also the base of the advertised AirPrint/DNS-SD name (`<name> @ <host>`) — while no Description is set; `PRINTER_DESCRIPTION` overrides the advertised name. |
|
||
| `PRINTER_DESCRIPTION` | *(empty)* | CUPS printer description (`lpadmin -D`), shown in the CUPS web UI. |
|
||
| `PPD_FILE` | `/usr/share/cups/model/BR4150_2.PPD` | PPD used for the queue. |
|
||
| `PRINTER_OPTIONS` | *(empty)* | Space-separated PPD option assignments (`key=value`) applied as queue defaults on every start, e.g. `BRColorMode=Vivid TonerSaveMode=False`. Discover the names with `lpoptions -p <queue> -l`. |
|
||
| `CERT_EXTRA_NAMES` | *(empty)* | Comma-separated extra DNS names and/or IP addresses to put into the generated `_ipps` certificate. |
|
||
| `CERT_DAYS` | `3650` | Validity of the generated certificate, in days. |
|
||
|
||
No queue is created when neither `PRINTER_IP` nor `PRINTER_URI` is set.
|
||
|
||
`PRINTER_OPTIONS` sets queue defaults that survive the recreation of the queue,
|
||
because they are re-applied on every start:
|
||
|
||
```sh
|
||
podman run -d --name printserver \
|
||
--network host \
|
||
-e PRINTER_IP=192.168.100.7 \
|
||
-e PRINTER_OPTIONS="BRMediaType=Plain BRColorMode=Vivid TonerSaveMode=False" \
|
||
printserver:latest
|
||
```
|
||
|
||
Entries that the PPD does not know are logged as a warning and ignored; the
|
||
remaining ones are applied and the container starts normally.
|
||
|
||
### Choosing the connection
|
||
|
||
The HL-4150CDN accepts PostScript directly, so the default is raw AppSocket.
|
||
Set `PRINTER_IP` and let the entrypoint build the URI:
|
||
|
||
| `PRINTER_PROTOCOL` | Resulting URI |
|
||
| --- | --- |
|
||
| `socket` (default) | `socket://<ip>:9100` — raw AppSocket/JetDirect |
|
||
| `lpd` | `lpd://<ip>/binary_p1` — LPR/LPD |
|
||
| `ipp` | `ipp://<ip>/ipp/print` |
|
||
|
||
For example, a printer at `192.168.100.7`:
|
||
|
||
```sh
|
||
-e PRINTER_IP=192.168.100.7
|
||
```
|
||
|
||
Or specify the URI explicitly for full control:
|
||
|
||
```sh
|
||
-e PRINTER_URI=socket://192.168.100.7:9100
|
||
```
|
||
|
||
### Advertised name
|
||
|
||
`PRINTER_NAME` is the CUPS queue name. It is also the base of the name clients
|
||
see in AirPrint discovery — CUPS advertises `"<name> @ <host>"` — but only as
|
||
long as no Description is set: `PRINTER_DESCRIPTION` (the `lpadmin -D`
|
||
Description) has precedence and becomes the advertised name instead:
|
||
|
||
```sh
|
||
-e PRINTER_NAME=HL4150CDN -e PRINTER_DESCRIPTION="Brother HL-4150CDN (Office)"
|
||
```
|
||
|
||
advertises the DNS-SD instance `Brother HL-4150CDN (Office) @ <host>`, not
|
||
`HL4150CDN @ <host>`. That instance name is what clients pin into their
|
||
`dnssd://` device URI, so every queue needs a **unique** Description: two
|
||
queues deriving the same name collide, and only one of them is ever advertised
|
||
(see *macOS shows the printer offline after some time* under Troubleshooting).
|
||
|
||
### TLS certificate for `_ipps`
|
||
|
||
CUPS advertises the queue twice over mDNS: `_ipp._tcp` (plain) and `_ipps._tcp`
|
||
(TLS). macOS and iOS prefer the secure one, and *validate the certificate
|
||
against the name they connected to*. The name clients see is Avahi's
|
||
`<host>.local`, which is not necessarily the container's host name — so the
|
||
entrypoint installs its own self-signed certificate instead of leaving it to
|
||
CUPS. Its SANs cover the container host name, `<host name>.local`, Avahi's
|
||
FQDN, `localhost`, `127.0.0.1` and everything in `CERT_EXTRA_NAMES`:
|
||
|
||
```sh
|
||
-e CERT_EXTRA_NAMES=192.168.100.20,print.example.org
|
||
```
|
||
|
||
The certificate lives in `/etc/cups/ssl` (so that directory must be writable:
|
||
use a named volume, see the bind-mount caveat under *Notes and limitations*)
|
||
and is only regenerated when one of the names it must cover is missing, so a
|
||
restart normally logs `TLS certificate is up to date`.
|
||
|
||
It is also copied — as `<name>.crt`/`<name>.key` — for every name CUPS may look
|
||
it up by, because CUPS 2.4 does not choose the certificate by the name the client
|
||
requested: it reverse-resolves the *local* address the client connected to and
|
||
loads `<name>.crt` from `/etc/cups/ssl`. A host-networked container resolves its
|
||
own LAN address to the name Podman writes into `/etc/hosts`
|
||
(`host.containers.internal`), and IPv6 literals are used as-is, so those names
|
||
need a copy too. Otherwise CUPS mints a certificate of its own for the address,
|
||
whose SANs do not cover the advertised `.local` name.
|
||
|
||
Inspect what the server actually presents:
|
||
|
||
```sh
|
||
podman cp printserver:/etc/cups/ssl/server.crt .
|
||
openssl x509 -in server.crt -noout -subject -ext subjectAltName
|
||
echo | openssl s_client -connect localhost:631 -servername "$(hostname).local" \
|
||
| openssl x509 -noout -subject -ext subjectAltName
|
||
```
|
||
|
||
Because the certificate is self-signed, a fully validating client (macOS/iOS
|
||
AirPrint, `curl` without `-k`) rejects it until you trust it. CUPS' own tools
|
||
(`lpadmin`, `ipptool`) are lenient, so plain printing works without this step:
|
||
|
||
- **macOS** (needs your password):
|
||
|
||
```sh
|
||
sudo security add-trusted-cert -d -r trustRoot \
|
||
-k /Library/Keychains/System.keychain ./server.crt
|
||
```
|
||
|
||
- **iOS/iPadOS**: AirDrop or mail `server.crt` to the device, install the
|
||
profile, then enable full trust under *Settings → General → About →
|
||
Certificate Trust Settings*.
|
||
|
||
Alternatively, keep using plain IPP (port 631 without TLS) — printing and
|
||
duplex/colour options work there too.
|
||
|
||
## Printing options
|
||
|
||
The BR-Script3 PPD exposes the HL-4150CDN's hardware options. Common ones:
|
||
|
||
| Option | PPD keyword | Values |
|
||
| --- | --- | --- |
|
||
| Paper source | `InputSlot` | `AutoSelect`, `Tray1`, `Tray2`, `MPTray` |
|
||
| Two-sided printing | `Duplex` | `None`, `DuplexNoTumble` (long edge), `DuplexTumble` (short edge) |
|
||
| Colour / mono | `BRPrintQuality` | `Auto`, `Color`, `Black` |
|
||
| Colour mode | `BRColorMode` | `Normal`, `Vivid`, `None` (default) |
|
||
| Media type | `BRMediaType` | `Plain`, `Thin`, `Thick`, `Recycled`, `Glossy`, `Label`, envelopes, … |
|
||
| Print quality | `CAPT` | `Fine` (600×600), `SuperFine` (2400 dpi class) |
|
||
| Toner save | `TonerSaveMode` | `True`, `False` |
|
||
| Secure print | `BRJobHold` | `None`, `Private` (+ `BRJobPIN`) |
|
||
|
||
They can be selected per job, e.g.:
|
||
|
||
```sh
|
||
podman exec printserver lp -d HL4150CDN \
|
||
-o Duplex=DuplexNoTumble -o BRPrintQuality=Color -o InputSlot=Tray1 report.pdf
|
||
```
|
||
|
||
### Why the macOS/iOS dialog shows fewer options
|
||
|
||
macOS and iOS print driverless (AirPrint / IPP Everywhere), so the dialog shows
|
||
only IPP-standard options: media size, duplex, pages per sheet, the
|
||
black-and-white/colour toggle and the quality slider. Brother-specific options
|
||
(`BRColorMode`, `BRMediaType`, `TonerSaveMode`, `CAPT`, …) have no IPP
|
||
equivalent and can never appear there. Set them server-side instead:
|
||
|
||
- as queue defaults at container start via `PRINTER_OPTIONS` (see
|
||
*Environment variables*),
|
||
- per job with `lp -o …`, or
|
||
- interactively in the CUPS web UI (`http://<host>:631/admin`, *Printing Options*).
|
||
|
||
The dialog's black-and-white/colour toggle maps to `print-color-mode` and the
|
||
quality slider to `print-quality`; the PPD presets documented below translate
|
||
both onto the Brother options, so they now really take effect:
|
||
|
||
| Dialog / IPP | PPD option it becomes |
|
||
| --- | --- |
|
||
| `print-color-mode=monochrome` | `BRPrintQuality=Black` |
|
||
| `print-color-mode=color` | `BRPrintQuality=Auto` |
|
||
| `print-quality=3` (draft) | `TonerSaveMode=True` |
|
||
| `print-quality=4` (normal) | defaults (`Fine`, toner save off) |
|
||
| `print-quality=5` (high) | `CAPT=SuperFine` (2400 dpi class) |
|
||
|
||
Client-specified values always win over the preset.
|
||
|
||
### Changes to the vendor PPD
|
||
|
||
`assets/Brother HL-4150CDN/ppd/BR4150_2.PPD` is Brother's original file plus
|
||
two additions, each marked with a `%` comment in the file:
|
||
|
||
- **`*DefaultBRColorMode: None` instead of `Normal`.** `Normal` and `Vivid` both
|
||
set `<</UseCIEColor true>>`; the printer's own CIE conversion washes the
|
||
colours out (see [Colours are printed pale / washed out](#colours-are-printed-pale--washed-out)).
|
||
- **Six `APPrinterPreset` entries** mapping `print-color-mode`
|
||
(color/monochrome) and `print-quality` (draft/normal/high) onto
|
||
`BRPrintQuality`, `CAPT` and `TonerSaveMode`. CUPS applies such presets for
|
||
driverless clients and always lets the client's own option values win. In
|
||
particular `print-color-mode=monochrome` becomes `BRPrintQuality=Black`, so
|
||
black-and-white printing works: the server cannot do the greyscale conversion
|
||
itself, because `pdftops` uses Poppler for Brother printers and Poppler
|
||
cannot produce greyscale (`WARNING: Grayscale/monochrome could be printed as
|
||
color with Poppler` in the CUPS log). The presets use the notation CUPS
|
||
generates itself — Apple metadata as `key value` pairs *without* `=`, PPD
|
||
options as `*Name Value` pairs on separate lines inside a multiline
|
||
`"…"` string. With `key=value` on a single line CUPS silently ignores the
|
||
whole preset (verified against libcups 2.4.2: `_ppdParseOptions()` splits on
|
||
whitespace, so `key=value` becomes the option *name* and the lookup fails).
|
||
Note that IPP `print-quality` is `3` (draft), `4` (normal), `5` (high).
|
||
|
||
## Client support
|
||
|
||
| Platform | Path | Notes |
|
||
| --- | --- | --- |
|
||
| macOS | AirPrint / IPP Everywhere | Discovered via mDNS; no driver needed. If *Use:* shows *Generic PostScript Printer*, see [Troubleshooting](#macos-adds-the-printer-as-a-generic-postscript-printer) |
|
||
| iOS / iPadOS | AirPrint | Discovered via mDNS; no driver needed |
|
||
| Linux | IPP / IPP Everywhere | Native or with the shared PPD |
|
||
| Windows | IPP or raw 9100 | Install Brother's **BR-Script3** Windows driver, pointed at the printer or the shared queue |
|
||
|
||
Windows uses a client-side Brother driver; the server does not share via SMB.
|
||
|
||
## Verify
|
||
|
||
Browse the CUPS web interface:
|
||
|
||
```sh
|
||
open http://localhost:631/ # printers
|
||
open http://localhost:631/admin # administration
|
||
```
|
||
|
||
From the host, or inside the container:
|
||
|
||
```sh
|
||
podman exec printserver lpstat -t # queues and status
|
||
podman exec printserver lpstat -v HL4150CDN # device URI
|
||
podman exec printserver lpoptions -p HL4150CDN -l # available options
|
||
podman exec printserver avahi-browse -rt _ipp._tcp # mDNS advertisement
|
||
```
|
||
|
||
A print job can be submitted with:
|
||
|
||
```sh
|
||
podman exec printserver lp -d HL4150CDN /etc/hostname
|
||
```
|
||
|
||
On macOS/iOS the shared queue should appear in the printer list via AirPrint.
|
||
|
||
## Notes and limitations
|
||
|
||
- **Host networking is required** for Avahi to send and receive mDNS traffic.
|
||
`-p 631:631` alone is enough for direct IPP access but not for AirPrint
|
||
discovery.
|
||
- **Printer state is not persisted by default.** The queue is recreated on every
|
||
start from `PRINTER_IP`/`PRINTER_URI`, so nothing has to survive a restart. To
|
||
keep CUPS state (extra queues, subscriptions) across container replacement,
|
||
mount a volume over `/etc/cups` — **use a named volume**:
|
||
|
||
```sh
|
||
-v printserver-cups:/etc/cups
|
||
```
|
||
|
||
Podman seeds a *new named volume* with the image's `/etc/cups`, so `cupsd.conf`
|
||
is present. A **bind mount to an empty host directory does not**: it hides the
|
||
image's configuration, `cupsd` then exits immediately (status 1, silently —
|
||
its default `ErrorLog` is syslog), the container stops, and the entrypoint's
|
||
`lpadmin` fails with a misleading
|
||
`lpadmin: Unable to connect to server: Bad file descriptor`. This is easiest
|
||
to hit from a quadlet that bind-mounts a share, e.g.
|
||
`Volume=/nfs/share/print-server/:/etc/cups/:Z`. If you must bind-mount, make
|
||
sure the directory already contains a `cupsd.conf`; also note that a read-only
|
||
share, NFS `root_squash` or SELinux relabeling can still prevent `cupsd` from
|
||
writing its state there. For a named volume in a quadlet, use
|
||
`Volume=printserver-cups:/etc/cups` (a bare name, no leading `/`).
|
||
- Colour handling is done by the printer's own PostScript RIP. Output may differ
|
||
slightly from Brother's host-side (rasterising) driver.
|
||
- The queue advertises AirPrint capabilities (`image/urf`, `image/pwg-raster`,
|
||
`sides-supported`, `color-supported`). macOS and iOS printing is converted by
|
||
cups-filters; verify end to end on your network.
|
||
|
||
## Troubleshooting
|
||
|
||
### The container runs but nothing answers on 631
|
||
|
||
The queue can be advertised over mDNS while nothing is actually listening, so
|
||
clients discover a printer they cannot reach. Check inside the container:
|
||
|
||
```sh
|
||
podman exec printserver curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:631/
|
||
podman exec printserver tail -20 /var/log/cups/error_log
|
||
```
|
||
|
||
**`Unable to open listen socket for address 0.0.0.0:631 - Permission denied`** —
|
||
`cupsd` never bound port 631. This is the normal outcome of **rootless Podman
|
||
with `Network=host`**: port 631 is privileged, and a rootless container does not
|
||
hold `CAP_NET_BIND_SERVICE` in the host network namespace (adding the capability
|
||
does not help, because the capability must belong to the *host* user namespace).
|
||
`cupsd` keeps running on its domain socket, so `lpadmin`/`lpstat` inside the
|
||
container still work — which makes the failure easy to miss. Fix one of:
|
||
|
||
- allow unprivileged binding of port 631 on the host, persistently (needs root):
|
||
|
||
```sh
|
||
echo 'net.ipv4.ip_unprivileged_port_start=631' | sudo tee /etc/sysctl.d/90-unprivileged-ports.conf
|
||
sudo sysctl --system
|
||
```
|
||
|
||
- run the container as a rootful (system) service rather than a user quadlet;
|
||
- or serve on an unprivileged port (CUPS advertises the actual port in its SRV
|
||
record, but be aware some AirPrint clients assume 631).
|
||
|
||
On SELinux systems, verify there is no port-labelling denial as well
|
||
(`sudo ausearch -m avc -ts recent`). Also ensure 631/tcp and 5353/udp are open in
|
||
the firewall.
|
||
|
||
### macOS/iOS sees the printer but reports it cannot connect
|
||
|
||
If plain IPP works but the *secure* connection fails, this is the `_ipps`
|
||
service: macOS prefers `_ipps._tcp`, which CUPS advertises unconditionally
|
||
(also when no certificate exists). The certificate lives in `/etc/cups/ssl`, so
|
||
that directory must be writable — use a **writable (named) volume**; see the
|
||
bind-mount caveat under *Notes and limitations*. If it is not writable, CUPS
|
||
logs `Unable to encrypt connection: Unable to create server credentials` and the
|
||
secure connection fails while plain IPP keeps working.
|
||
|
||
The other cause is the certificate itself: clients only accept it when the name
|
||
they connected to is in its SANs *and* they trust it (see *TLS certificate for
|
||
`_ipps`*). Typical symptoms: `curl https://<host>.local:631/` ends with
|
||
`(60) SSL certificate problem`, or `ipptool -tv ipps://…` succeeds while
|
||
macOS/iOS refuse. Add the missing name via `CERT_EXTRA_NAMES` or trust the
|
||
certificate. After changing anything, remove the printer in macOS and add it
|
||
again — macOS caches the failed attempt.
|
||
|
||
### macOS adds the printer as a Generic PostScript printer
|
||
|
||
Symptom: the queue prints, but *Use:* shows **Generic PostScript Printer** and
|
||
the print dialog offers no duplex, no colour and no tray 2 — while iOS, which
|
||
queries the printer live, offers all of them. On the Mac,
|
||
`lpoptions -p <queue> -l` lists only `APOptionalDuplexer` plus
|
||
`Duplex`/`PageSize`/`ManualFeed`/`InputSlot`, and
|
||
`/etc/cups/ppd/<queue>.ppd` contains `*PCFileName: "Generic.ppd"`.
|
||
|
||
macOS only takes its driverless AirPrint path when it accepts the printer's
|
||
secure `_ipps` service; if that fails it silently falls back to its bundled
|
||
generic PostScript driver. Duplex then stays hidden, because the generic PPD
|
||
gates it behind `APOptionalDuplexer False`. There are two causes; check the
|
||
Bonjour sub-types first.
|
||
|
||
**1. The printer is advertised with the CUPS `_cups` sub-type.** macOS classifies
|
||
a Bonjour printer as *shared* when it can be browsed under `_ipp._tcp,_cups`,
|
||
and its AirPrint driver is only offered for a printer that carries `_universal`
|
||
and no `_cups` (`-[DNSSDPrinter isShared]` / `-[DNSSDPrinter airprintMatch]` in
|
||
`AddPrinter.app`'s `PMBrowser.framework`). CUPS registers
|
||
`_cups,_print,_universal` unless `BrowseDNSSDSubTypes` says otherwise, so the
|
||
queue is offered with the generic driver only. `cupsd.conf` therefore sets
|
||
`BrowseDNSSDSubTypes _print,_universal`. Confirm it is in effect:
|
||
|
||
```sh
|
||
podman exec printserver avahi-browse -rt _ipp._tcp,_universal # lists the queue
|
||
podman exec printserver avahi-browse -rt _ipp._tcp,_cups # must not list it
|
||
```
|
||
|
||
The second command lists other CUPS servers on the network; only *this* queue
|
||
must be absent from it. If it does list the queue, the image's `cupsd.conf` did
|
||
not reach `/etc/cups` — a bind-mounted `/etc/cups` hides the image's copy; see
|
||
*Notes and limitations*.
|
||
|
||
**2. The certificate is rejected.** Clients only accept it when the name they
|
||
connected to is in its SANs *and* they trust it (see *TLS certificate for
|
||
`_ipps`*). Check what is advertised and served:
|
||
|
||
```sh
|
||
podman exec printserver avahi-browse -rt _ipps._tcp # what is advertised
|
||
podman cp printserver:/etc/cups/ssl/server.crt . &&
|
||
openssl x509 -in server.crt -noout -subject -ext subjectAltName
|
||
```
|
||
|
||
A way around either cause is to add the queue from the command line, driverless
|
||
over the same secure service (`ipptool -tv ipp://<host>:631/printers/<queue> <file-to-print>`
|
||
prints the UUID; drop the `urn:uuid:` prefix):
|
||
|
||
```sh
|
||
sudo lpadmin -x <queue>
|
||
sudo lpadmin -p <queue> -E -m everywhere \
|
||
-v "dnssd://<queue>%20%40%20<host>._ipps._tcp.local./?uuid=<printer-uuid>"
|
||
lpoptions -p <queue> -l # Duplex, ColorModel and tray 2 show up now
|
||
```
|
||
|
||
In the GUI, remove the printer and add it again, then pick **AirPrint** (not
|
||
*Generic PostScript Printer*) under *Use*. If only the generic entry is offered,
|
||
either the `_cups` sub-type is still being advertised or the secure connection is
|
||
still being rejected — fix that, or add the queue from the command line as
|
||
above. macOS caches the failed attempt, so remove and re-add the printer after
|
||
changing anything on the server.
|
||
|
||
### Colours are printed pale / washed out
|
||
|
||
Symptom: colour printouts look desaturated compared to the same PDF printed
|
||
directly from the Mac, or with Brother's own driver.
|
||
|
||
Cause: the PPD colour mode `Normal` (Brother's default) makes the printer's
|
||
PostScript interpreter apply its own CIE conversion (`<</UseCIEColor true>>`),
|
||
which washes colours out. `Vivid` sets `UseCIEColor` as well, so it does not
|
||
help either; `BRPrintQuality` (`BRColorAdapt`) and `TonerSaveMode` made no
|
||
visible difference in a side-by-side test. The container therefore ships the
|
||
PPD with `*DefaultBRColorMode: None` (`UseCIEColor` off), which produces
|
||
saturated output.
|
||
|
||
Check and, if needed, override the current default:
|
||
|
||
```sh
|
||
podman exec printserver lpoptions -p <queue> -l | grep BRColorMode # expect *None
|
||
podman exec printserver lp -d <queue> -o BRColorMode=None test.pdf
|
||
```
|
||
|
||
If output is still pale with `BRColorMode=None`, check the printer's own menu
|
||
(Toner → Toner Save off) and make sure `BRMediaType` matches the media in the
|
||
tray.
|
||
|
||
Black-and-white is a related but separate path: `print-color-mode=monochrome`
|
||
from a driverless client is mapped by the PPD presets onto
|
||
`BRPrintQuality=Black` (the printer renders mono). It is deliberately *not* done
|
||
by the server — with its Poppler renderer, `pdftops` cannot convert to
|
||
greyscale. Forcing Ghostscript with `-o pdftops-renderer=gs` would, but that
|
||
path is not recommended for Brother printers (bugs in their PostScript
|
||
interpreters).
|
||
|
||
### macOS shows the printer offline after some time
|
||
|
||
Symptom: the printer was added and printed fine, then macOS reports it
|
||
*offline* ("printer is not responding") some hours later — typically after the
|
||
container was restarted or after queues were edited on the server. Restarting
|
||
the container does not help; removing the printer on the Mac and adding it
|
||
again fixes it — until it happens again.
|
||
|
||
Cause: when macOS adds a Bonjour printer it pins the exact DNS-SD **instance
|
||
name** into the queue's device URI:
|
||
|
||
```text
|
||
dnssd://Laserdrucker%20HL-4150CDN%20%40%20podp._ipps._tcp.local./?uuid=…
|
||
└── "<Description> @ <host>" — must still resolve hours later
|
||
```
|
||
|
||
CUPS derives that name from the **Description**, not from the CUPS queue name
|
||
(the queue name is only the base while no Description is set). Two queues with
|
||
the same Description therefore derive the same instance name. Avahi can
|
||
register only one service per name, and CUPS 2.4 does not rename on collision
|
||
(upstream `scheduler/dnssd.c` still carries a `TODO` for it), so the losing
|
||
queue stays *Shared* but is never advertised. The Mac keeps resolving the name
|
||
that no longer exists and flips the queue to offline. The lost race is
|
||
deterministic — the same queue wins after every restart — which is why
|
||
restarting the container never helps, while remove/re-add re-pins the Mac to
|
||
the instance that is actually advertised.
|
||
|
||
The entrypoint logs a warning at startup when it finds such a collision. To
|
||
check by hand:
|
||
|
||
```sh
|
||
podman logs printserver 2>&1 | grep 'DNS-SD name' # entrypoint warning, if any
|
||
lpstat -v | grep dnssd # the instance name the Mac pinned
|
||
podman exec printserver lpstat -l -p # duplicate Descriptions?
|
||
dns-sd -B _ipp._tcp # one instance per name?
|
||
podman exec printserver avahi-browse -rt _ipps._tcp # what is really advertised
|
||
ipptool -tv ipp://<host>:631/printers/<queue> \
|
||
/usr/share/cups/ipptool/get-printer-attributes.test # server side itself
|
||
```
|
||
|
||
Two queues showing the same `Description:` while `dns-sd -B` lists only one
|
||
instance is the collision. Fix it on the server: give each queue a unique
|
||
Description, or remove the redundant queue:
|
||
|
||
```sh
|
||
lpadmin -h <host>:631 -p laserdrucker-im-buero -D "Laserdrucker Buero"
|
||
# or drop the duplicate entirely:
|
||
lpadmin -h <host>:631 -x laserdrucker-im-buero
|
||
```
|
||
|
||
Then remove the printer on the Mac and add it again — macOS re-pins the device
|
||
URI to the instance that is advertised now. The same remove/re-add is needed
|
||
after **every** Description change: the pinned name changes with it, and a
|
||
container restart alone cannot bring the old name back.
|
||
|
||
## CUPS 3 and the future of PPDs
|
||
|
||
Adding the queue with a PPD makes `lpadmin` print:
|
||
|
||
```
|
||
lpadmin: Printer drivers are deprecated and will stop working in a future version of CUPS.
|
||
```
|
||
|
||
This is an advance notice, not an error. OpenPrinting deprecated PPDs, printer
|
||
drivers and filter executables during the CUPS 2.x series, and **CUPS 3.x drops
|
||
them entirely** in favour of an all-IPP architecture (a *local* server for
|
||
temporary driverless queues plus a *sharing* server for persistent ones, with
|
||
filtering limited to PDF, PWG Raster, Apple Raster and PCLm). Any queue created
|
||
with a PPD (`-P`) or a driver model (`-m <driver>`) triggers the message — it is
|
||
not specific to Brother, and there is no way to keep the PPD and silence it. A
|
||
raw queue and `-m everywhere` (driverless) do *not* print it.
|
||
|
||
The driverless replacement is a **Printer Application**: a PAPPL-based service
|
||
that emulates a driverless IPP printer and does the format/option conversion on
|
||
the other side. For a PostScript device like the HL-4150CDN that role is filled
|
||
by the **PostScript Printer Application** (`ps-printer-app`), which takes over
|
||
exactly what our BR-Script3 PPD does today — the PPD does not disappear, it just
|
||
moves out of CUPS and into the printer application.
|
||
|
||
Note the HL-4150CDN is itself *not* IPP Everywhere / AirPrint capable (Brother
|
||
lists it as such), so the plain `-m everywhere` shortcut is not available here.
|
||
This container instead advertises AirPrint to its *clients* and performs the
|
||
PostScript conversion itself.
|
||
|
||
Nothing needs to change right now: the distro images used here ship CUPS 2.4.x
|
||
and PPD-based drivers keep working for years. When you eventually move to
|
||
CUPS 3.x, run `ps-printer-app` (or `legacy-printer-app`) alongside CUPS and
|
||
create the queue driverlessly against it (`ipp://…`), leaving the PPD inside the
|
||
printer application.
|