Fail loudly when CUPS cannot bind port 631
ci/woodpecker/push/build Pipeline was successful

The queue was still advertised over mDNS while cupsd never bound TCP 631:
a rootless Podman container on the host network cannot bind the privileged
port. Apple clients then discovered a printer they could not reach and
reported a confusing _ipps connection failure.

* entrypoint.sh: verify that CUPS accepts connections on 631 after startup
  and exit with an actionable message otherwise
* entrypoint.sh: mark the script executable in the repository
* cupsd.conf: drop BrowseRemoteProtocols, which CUPS 2.4 no longer supports
  and which logs an error on every start
* README: add a troubleshooting section for the port-binding failure and the
  _ipps/TLS certificate requirement, and document the /etc/cups bind-mount
  caveat plus the CUPS 3 PPD deprecation
This commit is contained in:
juergen committed 2026-10-18 21:07:35 +02:00
1 parent 4c90ff1290
commit 7f26ca4f2f
3 files changed
+121 -4

No files matched your search

+100 -3
View File
@@ -185,15 +185,112 @@ On macOS/iOS the shared queue should appear in the printer list via AirPrint.
- **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.** The queue is recreated on every start
from `PRINTER_URI`. To keep CUPS configuration across container replacement,
mount a volume over `/etc/cups`:
- **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). CUPS creates its TLS certificate lazily on
the first TLS connection, in `/etc/cups/ssl` — so that directory must be
writable. If it is not, CUPS logs
`Unable to encrypt connection: Unable to create server credentials` and the
secure connection fails while plain IPP keeps working. Use a **writable (named)
volume** for `/etc/cups`; see the bind-mount caveat under *Notes and
limitations*. After changing anything, remove the printer in macOS and add it
again — macOS caches the failed attempt.
## 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.
-1
View File
@@ -18,7 +18,6 @@ Listen /run/cups/cups.sock
# Advertise shared printers on the local network via mDNS/DNS-SD (Avahi).
Browsing On
BrowseLocalProtocols dnssd
BrowseRemoteProtocols none
BrowseWebIF Yes
# Default authentication type, when authentication is required...
Regular → Executable
+21
View File
@@ -111,6 +111,27 @@ if lpstat -p "${PRINTER_NAME}" >/dev/null 2>&1; then
log "queue ${PRINTER_NAME} is shared and set as default"
fi
# --- Verify CUPS is reachable on the network --------------------------------
# cupsd also runs with nothing but its domain socket, e.g. when it cannot bind
# the privileged port 631 (rootless Podman with host networking). It then still
# advertises the queue over mDNS, so clients see a printer they cannot reach.
# Detect that and fail loudly instead of reporting a healthy server.
if ! curl -fsS --noproxy '*' --max-time 2 -o /dev/null http://127.0.0.1:631/ 2>/dev/null; then
log "ERROR: cupsd is not listening on TCP 631 - network clients (AirPrint/IPP) cannot reach this server"
if [ -f /var/log/cups/error_log ]; then
log " recent CUPS errors:"
tail -n 5 /var/log/cups/error_log | sed 's/^/ /'
fi
log " If the log shows 'Unable to open listen socket for address 0.0.0.0:631 - Permission denied',"
log " port 631 is privileged and this rootless container may not bind it on the host network."
log " Either allow unprivileged binding of 631 (host, needs root), persistently:"
log " echo 'net.ipv4.ip_unprivileged_port_start=631' > /etc/sysctl.d/90-unprivileged-ports.conf"
log " sysctl --system"
log " or run this container as a rootful (system) service."
log " See the Troubleshooting section of the README."
exit 1
fi
log "ready: CUPS on 631/tcp, mDNS on 5353/udp"
# Keep the container running by waiting on cupsd.