This commit is contained in:
1 parent
50497780c7
commit
843c6866a5
7 files changed
+252
-5
No files matched your search
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-10-19
|
||||
@@ -0,0 +1,103 @@
|
||||
# Design
|
||||
|
||||
## Context
|
||||
|
||||
See `proposal.md` — Why.
|
||||
|
||||
The relevant facts, all from the running system (macOS 27.0.1 build 26A434,
|
||||
CUPS 2.4.2 in the container) and the CUPS sources:
|
||||
|
||||
- The container registers the queue with CUPS' own DNS-SD code
|
||||
(`scheduler/dirsvc.c`): `_printer._tcp` unconditionally, and `_ipp._tcp` /
|
||||
`_ipps._tcp` with the sub-type list from `DNSSDSubTypes`. The list is passed
|
||||
to one `DNSServiceRegister` call as `<type>,<subtypes>` (`snprintf(temp, "%s,%s",
|
||||
type, subtypes)`), so `DNSSDSubTypes` decides the whole sub-type set.
|
||||
- `DNSSDSubTypes` defaults to `_cups,_print,_universal` (`scheduler/conf.c`) and
|
||||
is settable with the `BrowseDNSSDSubTypes` directive.
|
||||
- The queue's TXT record already carries what a driverless client needs: `URF`,
|
||||
`pdl=…,image/urf`, `TLS=1.2`, `Color=T`, `Duplex=T`, `mopria-certified=1.3`.
|
||||
- macOS' Add Printer browser (`AddPrinter.app`'s `PMBrowser.framework`) browses
|
||||
`_ipp._tcp,_cups` **and** `_ipp._tcp,_universal` (plus the `_ipps` forms) and
|
||||
keys a printer's registration types by the `regType` the browse callback
|
||||
reports. `-[DNSSDPrinter isShared]` is YES when any of them ends in `,_cups`,
|
||||
and `-[DNSSDPrinter airprintMatch]` returns nothing when `isShared` is YES.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- The queue is advertised so that macOS classifies it as an AirPrint printer and
|
||||
offers the driverless driver, without changing anything else a client sees.
|
||||
|
||||
**Non-Goals:**
|
||||
- Replacing CUPS' DNS-SD advertising with hand-written Avahi service files, or
|
||||
changing the TXT record CUPS generates. CUPS' advertisement works for every
|
||||
other client; only the sub-type set is wrong.
|
||||
- Changing the certificate, the queue's PPD or the device URI.
|
||||
|
||||
## Decisions
|
||||
|
||||
**Drop `_cups` from `DNSSDSubTypes` instead of suppressing CUPS' registration.**
|
||||
|
||||
`BrowseDNSSDSubTypes _print,_universal` removes exactly the marker that makes
|
||||
macOS treat the queue as shared, while keeping the `_universal` sub-type that
|
||||
makes `isAirPrint` YES. `_print` is kept because CUPS ships it by default and
|
||||
dropping it would remove information some clients use, with no benefit here.
|
||||
|
||||
Alternatives considered:
|
||||
|
||||
- *Advertise the queue with a hand-written Avahi service definition and turn
|
||||
CUPS' own advertising off.* Gives full control over sub-types and TXT, but
|
||||
duplicates what CUPS already generates (including the UUID and `printer-type`
|
||||
TXT that CUPS clients resolve the queue with) and has to be kept in sync with
|
||||
the queues. Only worth it if the sub-type turns out not to be sufficient.
|
||||
- *Keep `_cups` and patch macOS' classification* — not possible; it is the
|
||||
client's behaviour.
|
||||
- *Stop sharing the printer (`printer-is-shared=false`)* — would remove the
|
||||
queue from mDNS entirely.
|
||||
|
||||
**Keep CUPS' TXT record and the `/cups` device URI.**
|
||||
|
||||
The `/cups` path marker and the `printer-type` TXT key come from CUPS' own
|
||||
advertisement and tell CUPS clients that this is a CUPS server; they are what
|
||||
makes `dnssd://…/cups?uuid=…` resolve. macOS' sub-type-based `isShared` is a
|
||||
separate mechanism from that marker, so removing the sub-type keeps CUPS
|
||||
clients working while fixing macOS' driver choice.
|
||||
|
||||
**Rely on `airprintMatch`'s remaining requirements being satisfied by the
|
||||
existing advertisement.**
|
||||
|
||||
After the sub-type fix, `airprintMatch` also needs a device-ID entry with the
|
||||
TXT-derived `URF:` key and a `dnssd servicetype:` entry for `_ipp._tcp` /
|
||||
`_ipps._tcp`, and a locally built generic URF PPD. Those come from the printer's
|
||||
TXT record, which already carries `URF` and `pdl` (PMBrowser normalises TXT keys
|
||||
to its `KEY:` form) and from the service type in the browse result — the same
|
||||
inputs a real AirPrint printer provides. If the GUI still offers only the
|
||||
generic driver, the fallback is the Avahi service file mentioned above.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [The queue is still classified as shared because the `/cups` device URI or
|
||||
`printer-type` TXT matter in a code path not covered by the disassembly] →
|
||||
Small risk; the disassembly of `isShared`/`isAirPrint`/`airprintMatch` covers
|
||||
the driver-choice gate. Verify on the client; fall back to an Avahi service
|
||||
file if needed.
|
||||
- [Some client relies on the `_cups` sub-type to identify the server] → CUPS'
|
||||
`dnssd` backend uses the TXT `printer-type`/`cups_shared` marker for that, not
|
||||
the sub-type, so the `/cups` URI is unchanged. Verify with `lpinfo -v` on a
|
||||
CUPS client.
|
||||
- [`cupsctl`'s rewrite of `/etc/cups/cupsd.conf` drops the directive] →
|
||||
`cupsAdminSetServerSettings()` copies unrecognised directives through
|
||||
verbatim (`cups/adminutil.c`), so `BrowseDNSSDSubTypes` survives the
|
||||
`cupsctl --share-printers --remote-admin` the entrypoint runs after cupsd has
|
||||
started. Confirmed by reading the write loop, not by a live run.
|
||||
- [macOS caches the previous classification for an existing printer entry] →
|
||||
Nothing the server can do; the printer has to be removed and added again. It
|
||||
is documented in the README.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Rebuild and redeploy the image; no volume reset. `cupsd` re-registers the
|
||||
queue's services on start and drops the `_cups` sub-type, so the change takes
|
||||
effect immediately (mDNS caching on clients aside). Rollback is redeploying the
|
||||
previous image; the extra sub-type reappears and macOS falls back to the generic
|
||||
driver as before.
|
||||
@@ -0,0 +1,68 @@
|
||||
# Proposal
|
||||
|
||||
## Why
|
||||
|
||||
macOS adds the queue with *Use:* = **Generic PostScript Printer** instead of the
|
||||
driverless **AirPrint Printer**, so duplex, colour and tray 2 stay hidden — the
|
||||
generic PPD gates duplex behind `APOptionalDuplexer False`. The certificate is
|
||||
no longer the cause: `curl https://podp.local:631/` succeeds, CUPS serves the
|
||||
expected certificate, and every IPP query the Mac makes against the queue
|
||||
succeeds (7/7 `HTTP_STATUS_OK, ipp status 0`). iOS, which queries the printer
|
||||
live, already offers every option.
|
||||
|
||||
The remaining cause is a Bonjour sub-type. Disassembling
|
||||
`AddPrinter.app/Contents/Frameworks/PMBrowser.framework` (macOS 27.0.1) shows the
|
||||
gate for the AirPrint driver entry:
|
||||
|
||||
- `-[DNSSDPrinter isShared]` returns YES as soon as any of the printer's
|
||||
registration types ends in `,_cups`; the registration types are the `regType`
|
||||
strings the Bonjour browse callback reports.
|
||||
- `-[DNSSDPrinter airprintMatch]` returns nothing when `isShared` is YES, and
|
||||
only builds the AirPrint driver when `isAirPrint` is YES — that is, when the
|
||||
service was browsed under `_ipp._tcp,_universal` or `_ipps._tcp,_universal`.
|
||||
- `-[DNSSDBrowser startBrowsing]` browses `_ipp._tcp,_cups` **and**
|
||||
`_ipp._tcp,_universal` (and the `_ipps` equivalents), so a printer that
|
||||
carries both sub-types is classified as *shared* and never gets the AirPrint
|
||||
driver.
|
||||
|
||||
CUPS registers `DNSSDSubTypes`, whose default is `_cups,_print,_universal`
|
||||
(`scheduler/conf.c`), which is exactly that combination. The queue is therefore
|
||||
advertised as a shared CUPS printer and macOS falls back to its generic driver.
|
||||
The sub-types are configurable with the `BrowseDNSSDSubTypes` directive.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Set `BrowseDNSSDSubTypes _print,_universal` in `cupsd.conf`, so the queue is
|
||||
advertised with the AirPrint (`_universal`) sub-type and without the CUPS
|
||||
(`_cups`) one. `_print` is kept for clients that look for it.
|
||||
- Keep CUPS' own advertising otherwise (the unconditional `_printer._tcp`
|
||||
registration, the `printer-type` TXT key and the resulting `/cups` device
|
||||
URI), so CUPS clients keep resolving the queue exactly as before.
|
||||
- Correct the README: the "macOS adds the printer as a Generic PostScript
|
||||
printer" section blames the certificate alone and recommends adding the queue
|
||||
from the command line; it now documents the sub-type cause first and how to
|
||||
confirm what is advertised.
|
||||
|
||||
Non-goals: hand-written Avahi service files, and patching CUPS' TXT record.
|
||||
Neither is needed unless the sub-type alone turns out to be insufficient.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
<!-- None: this fixes the advertisement required by an existing capability. -->
|
||||
|
||||
### Modified Capabilities
|
||||
- `printserver/cups`: the "Printer is shared and discoverable" requirement gains
|
||||
a scenario that fixes which Bonjour sub-types are advertised, because the
|
||||
advertised sub-types decide whether macOS offers the driverless AirPrint
|
||||
driver.
|
||||
|
||||
## Impact
|
||||
|
||||
- `cupsd.conf`: one directive plus an explanatory comment.
|
||||
- `README.md`: troubleshooting section corrected.
|
||||
- `openspec/specs/printserver/cups/spec.md`: one scenario added.
|
||||
- Clients: macOS should offer *AirPrint Printer* under *Use:*; the `_ipp`/`_ipps`
|
||||
services, the `/cups` device URI and Linux/Windows clients are unaffected.
|
||||
macOS caches the old classification per printer entry, so an existing entry
|
||||
has to be removed and added again.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Spec Delta
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### 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
|
||||
|
||||
#### Scenario: Advertised DNS-SD sub-types
|
||||
- **WHEN** the shared queue is advertised over mDNS
|
||||
- **THEN** its `_ipp._tcp` and `_ipps._tcp` registrations carry the `_print` and `_universal` sub-types and carry no `_cups` sub-type, so a client that classifies a printer carrying `_cups` as a shared CUPS printer — macOS, in its Add Printer window — still offers its driverless AirPrint driver
|
||||
@@ -0,0 +1,18 @@
|
||||
# Tasks
|
||||
|
||||
## 1. Advertise the AirPrint sub-type without the CUPS sub-type
|
||||
|
||||
- [x] 1.1 Add `BrowseDNSSDSubTypes _print,_universal` to `cupsd.conf` with the reason (macOS treats a printer carrying `_cups` as shared and then withholds its AirPrint driver), and verify the directive name, its default and the way it forms the registered sub-type list against the CUPS 2.4 sources (`scheduler/conf.c`, `scheduler/dirsvc.c`)
|
||||
- [x] 1.2 Correct the README "macOS adds the printer as a Generic PostScript printer" section so it names the sub-type cause first, keeps the certificate as the second cause, and shows commands that actually report the advertisement; verify the documented `avahi-browse` commands are valid invocations of the installed Avahi client
|
||||
- [ ] 1.3 Rebuild and redeploy the image on the podman host, then verify the advertisement: `podman exec systemd-print-server avahi-browse -rt _ipp._tcp,_universal` lists the queue and `avahi-browse -rt _ipp._tcp,_cups` does not list it (other CUPS servers on the network may appear there)
|
||||
- [ ] 1.4 Verify nothing else regressed: `ipptool -tv ipp://podp.local:631/printers/<queue>` and the `ipps://` variant report ipp status 0, and `lpinfo -v` on a CUPS client still resolves the queue to a `dnssd://…/cups?uuid=…` URI
|
||||
|
||||
## 2. Confirm macOS offers the driverless driver
|
||||
|
||||
- [ ] 2.1 On the Mac, remove the queue and add it again; verify the *Use:* popup offers **AirPrint Printer**, `/etc/cups/ppd/<queue>.ppd` contains `*APAirPrint: True` and `lpoptions -p <queue> -l` lists the duplex, colour and tray-2 options that the generic PPD hid
|
||||
|
||||
## Workflow follow-up
|
||||
|
||||
- Archive this change (`/opsx-archive`) so the delta merges into `openspec/specs/printserver/cups/spec.md`.
|
||||
- Archive the still-open `fix-ipps-certificate-lookup` change once its live verification task is done.
|
||||
- Commit the implementation together with the archived changes using `jj`.
|
||||
Reference in new issue
Block a user