diff --git a/README.md b/README.md index 05cd009..8641bd2 100644 --- a/README.md +++ b/README.md @@ -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 `) 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. diff --git a/cupsd.conf b/cupsd.conf index dafd7cc..e2e3f48 100644 --- a/cupsd.conf +++ b/cupsd.conf @@ -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... diff --git a/entrypoint.sh b/entrypoint.sh old mode 100644 new mode 100755 index 3fe2187..1cfb792 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -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.