Files
printserver/openspec/changes/warn-on-dnssd-name-collisions/design.md
T

99 lines
5.3 KiB
Markdown

# Design
## Context
`entrypoint.sh` starts D-Bus, Avahi and cupsd, configures the managed queue
(`lpadmin -D/-o/-E` on every start) and then verifies cupsd answers on TCP 631.
Any queue present in cupsd — including ones added manually via the web UI — is
advertised as the DNS-SD instance `"<Description> @ <host>"` (queue name when no
Description is set). Avahi registers only one instance per name: with two
queues deriving the same name, one is silently never advertised, and CUPS 2.4
does not rename on collision (upstream `scheduler/dnssd.c`, `TODO:
Handle collisions`). Clients that pinned the losing name (macOS stores it in
the `dnssd://` device URI) then report the printer offline. See proposal.md for
the incident. The container ships `cups-client` (guaranteed `lpstat`),
`avahi-utils` and `curl` (Containerfile).
## Goals / Non-Goals
**Goals:**
- At startup, detect when two or more queues would derive the same DNS-SD
instance name and log an actionable warning naming the queues.
- Keep the check strictly best-effort: it must never affect startup success or
queue configuration (consistent with the existing warning style under
`set -eu`).
- Give operators a documented way to confirm and fix the condition by hand
(README troubleshooting entry).
**Non-Goals:**
- No automatic remediation (no renaming, no un-sharing, no deleting queues).
- No change to how/when `lpadmin` runs — the restart re-registration churn is
out of scope for this change.
- No client-side tooling; the check runs only inside the container.
## Decisions
**D1 — Derive names from live cupsd data, not from mDNS.**
The check compares *derived* instance names (`Description` or queue name per
queue) instead of listening to actual Avahi registrations. Derivation is
deterministic and race-free at startup; observing mDNS (`avahi-browse`) would
depend on registration timing and only detect collisions after the fact.
- *Alternative considered:* post-startup mDNS audit ("shared queue missing from
advertisements") — catches more failure modes but is timing-sensitive and
needs instance→queue mapping via TXT `rp`; rejected as the primary mechanism,
still documented as a manual verification step in the README.
**D2 — Inputs: `LC_ALL=C lpstat -l -p` for names/Descriptions, `printers.conf`
for the shared flag.**
- Queue names + Descriptions come from `lpstat -l -p` run with `LC_ALL=C` so the
`Description:` line parses regardless of container locale (the live value is
authoritative — it is exactly what cupsd will register).
- Only shared queues are advertised, so the shared flag comes from
`/etc/cups/printers.conf` (`<Printer …>` block: `Shared yes`). When the flag
cannot be determined (file not yet flushed after this run's `lpadmin`, queue
missing from it), the queue is **treated as shared** — a collision warning
for a not-yet-shared queue is still actionable, whereas staying silent on a
shared one reproduces the original incident.
- *Alternative considered:* `ipptool` Get-Printer-Attributes — richer data, but
availability of `ipptool` in the image is not guaranteed by the Containerfile
package list; rejected to keep the check dependency-free beyond `cups-client`.
**D3 — Placement and failure mode.**
A `check_dnssd_name_collisions` function runs after the queue configuration
blocks and the TCP reachability check, immediately before the final `ready`
log line, so the warning is the last thing an operator sees in `podman logs`.
It logs through the existing `log()` helper (`[entrypoint] warning: …`
followed by indented detail lines, mirroring the TCP-631 error block) and never
exits non-zero; every parse failure degrades to "check skipped" (the script
runs under `set -eu`, so all command substitutions are guarded with `|| true`).
**D4 — Collision = exact match on the name base.**
The `@ <host>` suffix is identical for every queue on the server, so equality
is decided on the base alone; the full derived name (base + `@ <short-host>`)
is still printed in the warning so it matches what `dns-sd -B` shows.
The remedy text offers the two fixes that worked on the live system: give each
queue a unique Description (`lpadmin -D`), or remove the redundant queue
(`lpadmin -x`), followed by remove/re-add of the client queue (README details
why clients must be re-added).
## Risks / Trade-offs
- [Queues created *after* startup are not re-checked] → acceptable: the check
runs on every container start, and the README documents a manual
`dns-sd -B _ipp._tcp` verification for live changes.
- [Stale shared flag if `printers.conf` is not flushed in time] → mitigated by
assuming shared when unknown (errs toward warning), see D2.
- [False warning when a shared flag parse misreads a queue block] → parsing is
scoped to `<Printer name>` … `</Printer>` blocks with exact keyword matches;
a misread can at worst add a queue to the comparison, and the warning is
advisory only.
- [Warning becomes noise on intentionally duplicated setups] → duplication is
never functional in CUPS/Avahi (one registrant always loses), so there is no
legitimate configuration the warning would nag about.
## Migration Plan
Log-output-only change: deploy by rebuilding the image and restarting the
container (normal quadlet cycle). No state, config or client impact; rollback
is reverting `entrypoint.sh`/README.