Archive the add-cups-printserver-container change

Moves the completed change to openspec/changes/archive/2026-10-18-add-cups-printserver-container/ and syncs the printserver/cups capability into openspec/specs/.
This commit is contained in:
juergen committed 2026-10-18 16:54:54 +02:00
1 parent 30c685e085
commit adaa11e416
6 files changed
+123

No files matched your search

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-17
@@ -0,0 +1,105 @@
# Design
## Context
See proposal.md. We need a Podman container that runs CUPS for the Brother
HL-4150CDN network printer and shares it over IPP/AirPrint.
Facts established while exploring the device at `192.168.100.7`:
- The printer is a Brother HL-4150CDN (`Ver.1.22`) with PCL6 and **BR-Script3
(PostScript 3)** emulation — `@PJL INFO CONFIG` lists `POSTSCRIPT`.
- Open services: `:9100` (raw/AppSocket), `:515` (LPR/LPD), `:631` (IPP/IPPS)
and `:80` (Brother web UI). It has one paper cassette plus the MP tray.
- A raw PostScript job sent to `:9100` is interpreted by the printer, and a full
CUPS job with `Duplex=DuplexNoTumble BRPrintQuality=Color` printed correctly
(duplex, colour).
An earlier iteration used Brother's Linux packages. That vendor CUPS chain is
broken in `debian:bookworm-slim`: `filterhl4150cdn` needs `file` (and `a2ps` as
a fallback), which are absent, so the filter exits 1 and emits zero bytes. That
chain also rasterises locally (Ghostscript → `ppmraw` → `brhl4150cdnfilter`) and
stores per-queue options in a single global file (`brhl4150cdnrc`), which is
heavy and racy. The packages have since been removed from the repository.
Crucially, the Brother **vendor** PPD (`brhl4150cdn.ppd`) cannot be used for
direct PostScript because its option values are blank (`" "`); all
option handling lives in the vendor filter. Brother's **BR-Script3 PPD**, in
contrast, encodes every option as PostScript `setpagedevice`.
## Goals / Non-Goals
**Goals:**
- Build a working CUPS image for Podman with no 32-bit dependencies.
- Drive the printer with its native PostScript (BR-Script3) interpreter.
- Preserve the hardware options the user cares about: paper tray, duplex and
colour/mono (plus the other options the PPD exposes).
- Share the queue over IPP and advertise it via AirPrint.
- Keep the printer address/name runtime-configurable.
**Non-Goals:**
- Managing multiple printers.
- Sharing via SMB/Samba (Windows uses a client-side Brother driver).
- Persisting CUPS spool/state beyond the container lifecycle.
- TLS for the CUPS web interface.
## Decisions
### Base image and packages
Use `debian:bookworm-slim` with `cups`, `cups-client`, `cups-filters`,
`avahi-daemon`, `avahi-utils`, `libnss-mdns`, `dbus`, `ghostscript` and
`poppler-utils`. No `dpkg --add-architecture i386` and no Brother `.deb`
packages: the job is converted to PostScript in CUPS and interpreted by the
printer, so no vendor binary is needed.
_Alternative considered:_ keep Brother's Linux CUPS wrapper + LPR filter. It
provides the same options but requires 32-bit libraries, Ghostscript
rasterisation and the missing `file`/`a2ps` tools, and it writes options to a
single global config file. Rejected.
### Print path: direct PostScript via the BR-Script3 PPD
Install Brother's official BR-Script3 PPD as
`/usr/share/cups/model/BR4150_2.PPD` (committed at
`assets/Brother HL-4150CDN/ppd/BR4150_2.PPD`, extracted from the vendor Windows
driver) and create the queue with it. CUPS's `pstops` filter reads the PPD and
injects the selected option code (e.g. `/BRFeeder`, `/Duplex`,
`/BRProcessColor`, `/MediaType`) into the job; the PPD's `*JCLBegin`,
`*JCLToPSInterpreter` and `*JCLEnd` entries wrap it in PJL. The result is
forwarded unchanged to the printer.
A stock PostScript PPD (like CUPS's own `generic.ppd`) has no `*cupsFilter`
either, and CUPS accepts such PPDs — the printer's native format is
`application/vnd.cups-postscript`.
### Device URI
Use raw AppSocket as the default: `socket://<ip>:9100`. `lpd://<ip>/binary_p1`
and `ipp://…` remain available through `PRINTER_PROTOCOL`/`PRINTER_URI`.
### Client support
- macOS / iOS / iPadOS: AirPrint → CUPS. The queue advertises `image/urf`,
`image/pwg-raster`, `application/pdf` and `application/postscript`, with
`urf-supported = V1.4,CP1,W8,PQ4,SRGB24,RS600,DM1,FN3`, `sides-supported`
(one-sided/two-sided long/short edge) and `color-supported = true`.
- Linux: IPP / IPP Everywhere, or plain IPP with PDF/PostScript.
- Windows: client-side Brother BR-Script3 Windows driver, pointed at the
printer directly or at the shared queue.
### Runtime configuration
The printer endpoint and advertised name come from environment variables
(`PRINTER_IP`, `PRINTER_PROTOCOL`, `PRINTER_URI`, `PRINTER_NAME`,
`PRINTER_DESCRIPTION`, `PPD_FILE`) rather than being baked into the image. The
entrypoint derives the URI and runs `lpadmin` on startup, then shares the queue.
## Risks / Trade-offs
- [Risk] The AirPrint path (URF → PDF → PostScript) relies on cups-filters'
`rastertopdf`; it was not exercised with a real URF job during exploration.
macOS/iOS printing should be verified end to end.
- [Risk] Colour handling now relies on the printer's PostScript RIP rather than
Brother's host-side colour tables; output may differ slightly from the vendor
raster driver.
- [Risk] The committed PPD is a Windows PPD and contains no CUPS-specific keys.
CUPS accepts it as a PostScript PPD (verified for this CUPS version), but this
should be re-checked on CUPS upgrades.
- [Risk] Avahi needs host networking (or equivalent) to advertise mDNS.
- [Risk] CUPS state is not persisted; the queue is recreated on every start.
@@ -0,0 +1,57 @@
# Proposal
## Why
Run a Podman container that shares the Brother HL-4150CDN network printer over
IPP/AirPrint, so clients on macOS, iOS/iPadOS, Linux and Windows can print.
The HL-4150CDN already emulates BR-Script3 (PostScript 3) and accepts PostScript
on its AppSocket/LPD ports, so the container can forward PostScript directly to
the printer instead of shipping Brother's 32-bit Linux CUPS wrapper plus
Ghostscript rasterisation. This removes the i386 dependency, the multiarch
runtime and the fragile vendor filter chain.
The previous approach (Brother Linux `.deb` packages) turned out not to work in
`debian:bookworm-slim`: the vendor filter `filterhl4150cdn` needs `file` (with
`a2ps` as a fallback), which are not installed, so it exits with an error and
produces no output. The official BR-Script3 PPD avoids that entire code path.
## What Changes
- Create a Containerfile for Podman based on Debian stable running CUPS, Avahi
and D-Bus (no i386 multiarch).
- Install Brother's official **BR-Script3 PPD** (committed under
`assets/Brother HL-4150CDN/ppd/`) so CUPS emits device options — paper source
(tray), duplex and colour/mono, plus media type, print quality, toner save and
secure print — as PostScript `setpagedevice`.
- Point the queue at the printer with a raw AppSocket URI (`socket://<ip>:9100`)
by default.
- Convert incoming client formats (URF/Apple Raster, PWG Raster, PDF, PostScript)
to PostScript inside CUPS and pass them through to the printer's PostScript
interpreter.
- Share the queue over IPP and advertise it via mDNS/DNS-SD (Avahi/AirPrint).
- Keep runtime configuration (printer address, protocol, queue name) in
environment variables.
- Document the supported client matrix.
## Capabilities
### New Capabilities
- `printserver/cups`: Containerised CUPS print server which shares the Brother
HL-4150CDN over IPP/AirPrint and prints by sending PostScript (BR-Script3)
directly to the printer.
### Modified Capabilities
None
## Impact
- Containerfile plus supporting `cupsd.conf` and `entrypoint.sh` in the
repository root.
- Uses `assets/Brother HL-4150CDN/ppd/BR4150_2.PPD`.
- Runtime ports: CUPS/IPP (631/tcp), mDNS/DNS-SD (5353/udp).
- No 32-bit runtime libraries; the image no longer depends on the host CPU
architecture.
- Windows clients install Brother's own BR-Script3 Windows driver client-side.
- The unused Brother Linux `.deb` packages previously under
`assets/Brother HL-4150CDN/` have been removed.
@@ -0,0 +1,124 @@
# Spec Delta
## Purpose
Provide a containerized CUPS print server that shares the Brother HL-4150CDN
network printer as an AirPrint/IPP printer and prints by sending PostScript
directly to the printer, so clients can print without a server-side vendor
driver.
## ADDED Requirements
### Requirement: Container image builds with required dependencies
The container SHALL include CUPS, cups-filters, Avahi/mDNS, D-Bus and the
conversion tools needed to turn client documents into PostScript, and SHALL NOT
require any 32-bit runtime libraries.
#### Scenario: Image contains CUPS
- **WHEN** the image is built
- **THEN** CUPS is installed and available at /usr/sbin/cupsd
#### Scenario: No 32-bit runtime required
- **WHEN** the image is built
- **THEN** the image contains no i386 multiarch libraries and no Brother vendor binaries
### Requirement: Brother BR-Script3 PPD is installed
The container SHALL install Brother's official HL-4150CDN BR-Script3 PPD from
the committed asset so CUPS can emit device options as PostScript.
#### Scenario: PPD present
- **WHEN** the image is built
- **THEN** the BR-Script3 PPD is present in the CUPS model directory
#### Scenario: CUPS recognises the model
- **WHEN** the queue is created with the PPD
- **THEN** CUPS reports the printer make and model as "Brother HL-4150CDN BR-Script3"
### Requirement: Hardware options are sent to the printer
The system SHALL translate the PPD's device options into PostScript
`setpagedevice` instructions so the printer's own hardware options are used.
#### Scenario: Paper source
- **WHEN** a job selects an input slot (auto select, a tray or the MP tray)
- **THEN** the emitted PostScript selects the corresponding paper source on the printer
#### Scenario: Duplex
- **WHEN** a job selects long-edge or short-edge two-sided printing
- **THEN** the emitted PostScript sets `/Duplex` and `/Tumble` accordingly
#### Scenario: Colour or mono
- **WHEN** a job selects colour or mono
- **THEN** the emitted PostScript sets the corresponding colour/mono device parameters
### Requirement: Jobs are printed via direct PostScript
The container SHALL forward the converted job as PostScript to the printer
without a server-side vendor filter or host-side rasterisation.
#### Scenario: Default device URI
- **WHEN** the container is started with a printer IP and no explicit URI
- **THEN** the queue is created with a raw AppSocket URI (`socket://<ip>:9100`)
#### Scenario: A job is printed
- **WHEN** a print job is submitted to the queue
- **THEN** the job is delivered to the printer as PostScript and printed by the printer's PostScript interpreter
### Requirement: Printer is shared and discoverable
The system SHALL share the queue over IPP and advertise it via AirPrint
(mDNS/DNS-SD).
#### Scenario: IPP printer is shared
- **WHEN** CUPS runs in the container
- **THEN** the printer is shared via IPP for network clients
#### Scenario: AirPrint discovery
- **WHEN** Avahi runs alongside CUPS
- **THEN** the printer is advertised via AirPrint with URF, sides and colour capabilities so macOS and iOS/iPadOS clients can discover it without a driver
### Requirement: Supported client platforms
The shared printer SHALL be usable from macOS, iOS/iPadOS, Linux and Windows.
#### Scenario: macOS and iOS/iPadOS
- **WHEN** a macOS or iOS/iPadOS client prints via AirPrint
- **THEN** the job is converted to PostScript and printed
#### Scenario: Linux
- **WHEN** a Linux client prints over IPP
- **THEN** the job is converted to PostScript and printed
#### Scenario: Windows
- **WHEN** a Windows client prints
- **THEN** it uses a client-side BR-Script3 driver against the printer or the shared queue
### Requirement: Runtime configuration is exposed
The container SHALL expose CUPS on port 631 and mDNS on UDP port 5353.
#### Scenario: CUPS port exposed
- **WHEN** the container runs
- **THEN** port 631 is available for CUPS administration and IPP
#### Scenario: mDNS port exposed
- **WHEN** the container runs
- **THEN** UDP port 5353 is available for AirPrint discovery
### Requirement: Printer endpoint and advertised name are configurable at runtime
The container SHALL accept the network printer address and the advertised printer name through environment variables so the same image can target a different printer or name without rebuilding.
#### Scenario: Printer IP is provided
- **WHEN** the container is started with `PRINTER_IP` set
- **THEN** a device URI is derived from that address and the Brother HL-4150CDN queue is created pointing at it
#### Scenario: Printer protocol is selected
- **WHEN** the container is started with `PRINTER_IP` and `PRINTER_PROTOCOL`
- **THEN** the device URI uses that protocol (socket, lpd or ipp), defaulting to socket
#### Scenario: Full URI override
- **WHEN** `PRINTER_URI` is set
- **THEN** it is used as the device URI and `PRINTER_IP` is ignored
#### Scenario: Custom advertised name
- **WHEN** the container is started with `PRINTER_NAME`
- **THEN** the CUPS queue uses that name and it is the name advertised via AirPrint/DNS-SD
#### Scenario: Custom CUPS description
- **WHEN** the container is started with `PRINTER_DESCRIPTION`
- **THEN** the queue's CUPS Description (Info) is set to that value
@@ -0,0 +1,33 @@
# Tasks
## 1. Repository initialization
- [x] 1.1 Initialize jujutsu repository with `jj git init` in repo root and verify `.jj` directory exists
## 2. BR-Script3 PostScript PPD asset
- [x] 2.1 Extract Brother's official HL-4150CDN BR-Script3 PPD (`eng/BR4150_2.PPD`) from the vendor Windows driver archive and commit it under `assets/Brother HL-4150CDN/ppd/BR4150_2.PPD`
- [x] 2.2 Document the provenance and the option→`setpagedevice` mapping in `assets/Brother HL-4150CDN/ppd/README.md`
## 3. Container build
- [x] 3.1 Create Containerfile based on Debian stable with CUPS, cups-filters, Avahi, D-Bus, Ghostscript and Poppler (no i386 multiarch)
- [x] 3.2 Install the BR-Script3 PPD into the CUPS model directory in the Containerfile
- [x] 3.3 Remove the Brother vendor `.deb` installation, i386 multiarch and the 32-bit loader setup from the Containerfile
- [x] 3.4 Keep cupsd.conf (listen on all interfaces, share printers, DNS-SD) and the required runtime directories
- [x] 3.5 Set up the startup script to run dbus, avahi-daemon and cupsd, and create the queue
- [x] 3.6 Expose ports 631/tcp and 5353/udp in the Containerfile
## 4. Printer configuration
- [x] 4.1 Create the queue with the BR-Script3 PPD and a raw AppSocket URI by default (`socket://<ip>:9100`)
- [x] 4.2 Keep environment-based configuration (`PRINTER_IP`, `PRINTER_PROTOCOL`, `PRINTER_URI`, `PRINTER_NAME`, `PRINTER_DESCRIPTION`, `PPD_FILE`)
- [x] 4.3 Share the queue and set it as default; verify AirPrint capabilities (URF, sides, colour)
## 5. Documentation and testing
- [x] 5.1 Update README for the PostScript/BR-Script3 approach and the client matrix (macOS, iOS/iPadOS, Linux, Windows)
- [x] 5.2 Build the image with podman and verify it builds
- [x] 5.3 Run the container and verify CUPS is reachable and the queue is shared/discoverable
- [x] 5.4 Submit a job (colour + duplex) and verify it prints correctly
- [x] 5.5 Verify `PRINTER_IP`/`PRINTER_PROTOCOL`/`PRINTER_NAME` derive the expected queue URI and advertised name
## 6. Cleanup
- [x] 6.1 Add `.gitignore` for macOS metadata and stop tracking existing `.DS_Store` files
- [x] 6.2 Remove the unused Brother Linux vendor packages (`lpr-driver/`, `cupswrapper/`, `installer/`) from the repository
- [x] 6.3 Prune the obsolete `printserver:amd64` image, the test containers and scratch files