add warn on dnssd name collision change spec

This commit is contained in:
juergen committed 2026-10-20 10:05:00 +02:00
1 parent 30670de93b
commit 3910ab974c
5 files changed
+296

No files matched your search

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-20
@@ -0,0 +1,98 @@
# 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.
@@ -0,0 +1,59 @@
# Proposal
## Why
On the live deployment (`podp`), two queues ended up with the identical CUPS
Description, so both derived the same DNS-SD instance name
(`"<Description> @ <host>"`). Avahi can register only one of them and CUPS 2.4
never resolves the collision (upstream `scheduler/dnssd.c` still has
`/* TODO: Handle collisions with avahi_alternate_service_name */`), so one queue
showed `printer-is-shared=true` but was never advertised. macOS pins the exact
instance name into the queue's `dnssd://` device URI; when the pinned name stops
being announced, the Mac reports the printer **offline**. Because the collision
is deterministic, container restarts never helped and only remove/re-add on the
client fixed it — hours of debugging with no server-side signal pointing at the
cause.
## What Changes
- **`entrypoint.sh`**: after cupsd is up, derive the DNS-SD instance name of
every shared queue (`Description` if set, otherwise the queue name, plus
`@ <host>`) and log a prominent, non-fatal warning when two or more queues
derive the same instance name — naming the affected queues and the fix
(give each queue a unique Description, or delete the redundant queue).
- **`README.md`**: new Troubleshooting entry "macOS shows the printer offline
after some time" documenting the symptom, root cause (client-side instance
name pinning + duplicate-Description collision), verification commands
(`dns-sd -B`, `lpstat -l -p`, `ipptool`), and the fix including the
remove/re-add step on macOS.
- **Docs correction**: the `PRINTER_NAME` description in the `entrypoint.sh`
header and in the README currently claims it is the "advertised (DNS-SD)
name" — that is only true while no Description is set; the Description
overrides it. Correct both places, and clarify the matching requirement in
the `printserver/cups` spec (same claim).
Out of scope: changing how queues are configured (no automatic renaming or
de-duplication), and avoiding the `lpadmin` re-registration churn on restart.
## Capabilities
### New Capabilities
(none)
### Modified Capabilities
- `printserver/cups`: adds a requirement that the container detects DNS-SD
instance-name collisions between shared queues at startup and reports them in
the container log, so a queue that silently loses its advertisement is visible
to the operator instead of surfacing as an "offline" client hours later.
## Impact
- `entrypoint.sh` — one additional startup diagnostic block (log output only;
no change to queue creation, advertisement, or exit behaviour).
- `README.md` — one new Troubleshooting section; correction of the
`PRINTER_NAME` wording.
- No changes to `cupsd.conf`, the `Containerfile`, or runtime behaviour seen by
clients; the fix for an existing collision is still a manual operator action
(documented in the README).
@@ -0,0 +1,47 @@
# Spec Delta
## ADDED Requirements
### Requirement: DNS-SD name collisions are reported at startup
The container SHALL, once CUPS is running, derive the DNS-SD instance name of
every shared queue — its Description if set, otherwise its queue name, suffixed
with `@ <host>` — and SHALL log a warning when two or more queues derive the
same instance name. The warning SHALL name the affected queues and the remedy,
and SHALL NOT prevent the container from starting.
#### Scenario: Two queues derive the same instance name
- **WHEN** two shared queues have Descriptions (or queue names) that derive the same DNS-SD instance name
- **THEN** startup logs a warning naming the colliding instance name, both affected queues and the remedy (a unique Description per queue, or removing the redundant queue), and the container continues to start normally
#### Scenario: A queue without a Description collides with another queue's Description
- **WHEN** one shared queue has no Description and would advertise its queue name while another shared queue's Description yields the same instance name
- **THEN** the collision is reported the same way, because both derive the same advertised name
#### Scenario: All derived instance names are unique
- **WHEN** every shared queue derives a distinct DNS-SD instance name
- **THEN** no collision warning is logged and startup proceeds as before
## MODIFIED Requirements
### 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 the advertised DNS-SD instance name (`<name> @ <host>`) is derived from it unless a Description is also set
#### Scenario: Custom CUPS description
- **WHEN** the container is started with `PRINTER_DESCRIPTION`
- **THEN** the queue's CUPS Description (Info) is set to that value, and the advertised DNS-SD instance name is derived from the Description instead of the queue name
@@ -0,0 +1,90 @@
# Tasks
## 1. entrypoint: Erkennung von DNS-SD-Namenskollisionen
- [ ] 1.1 `entrypoint.sh`: Funktion `check_dnssd_name_collisions` ergänzen,
aufgerufen nach dem TCP-631-Check und vor
`log "ready: CUPS on 631/tcp, mDNS on 5353/udp"`. Queue-Namen und
Descriptions via `LC_ALL=C lpstat -l -p` lesen (Locale-fest, live aus
cupsd), Shared-Flag je Queue aus `/etc/cups/printers.conf`
(`<Printer …>`-Block, `Shared yes`); unbekannter Shared-Status zählt als
geteilt. Pro Queue den Anzeigenamen ableiten: Description, sonst
Queue-Name.
Verifikation: `sh -n entrypoint.sh` läuft fehlerfrei,
`grep -n 'check_dnssd_name_collisions' entrypoint.sh` zeigt Definition
und Aufruf an der vorgesehenen Stelle.
- [ ] 1.2 Bei gleichen abgeleiteten Namen pro Kollisionsgruppe über den
bestehenden `log()`-Stil warnen (Mehrzeiler, analog zum TCP-631-Block):
kollidierender Anzeigename `<basis> @ <host>`, betroffene Queue-Namen,
Abhilfe (einzigartige Description je Queue per `lpadmin -D` oder
redundante Queue entfernen per `lpadmin -x`, danach Client-Queue auf dem
Mac entfernen und neu hinzufügen — Details im README). Bei eindeutigen
Namen keine Warnung ausgeben.
Verifikation: `grep -n 'warning:' entrypoint.sh` findet die neuen
Warnzeilen inkl. `lpadmin -D`-Remedies; das Verhalten selbst prüfen die
Aufgaben 3.2 (Kollision) und 3.3 (keine Kollision).
- [ ] 1.3 Alle Parseschritte abfangen, ohne dass `set -eu` den Start abbricht
(`|| true` / geblockte Substitutionen): fehlendes oder unlesbares
`printers.conf`,
fehlerhaftes `lpstat` → Prüfung still überspringen oder als geteilt
annehmen, niemals `exit` und niemals Abbruch der Queue-Einrichtung.
Verifikation: `sh -n entrypoint.sh` fehlerfrei; Startverhalten mit
fehlendem `printers.conf` ist in 3.4 abgedeckt.
## 2. README: Troubleshooting und PRINTER_NAME-Korrektur
- [ ] 2.1 `README.md`: neuen Troubleshooting-Abschnitt
„macOS shows the printer offline after some time" (englisch, Stil der
bestehenden `###`-Abschnitte) einfügen: Symptom (Queue existiert,
Status offline, erst nach Stunden/Wechseln sichtbar), Ursache
(macOS pinnt den exakten DNS-SD-Instanznamen `"<Description> @ <host>"`
in die `dnssd://`-Device-URI; zwei Queues mit gleicher Description
kollidieren, Avahi registriert nur eine, CUPS 2.4 benennt nicht um),
Diagnose-Befehle (`podman logs` Warnung, `dns-sd -B _ipp._tcp`,
`avahi-browse -rt _ipp._tcp`, `lpstat -l -p`, `ipptool -tv`), Heilung
(eindeutige Description bzw. `-x`, dann Remove/Re-add auf macOS — mit dem
Hinweis, warum Restart allein nicht hilft: Kollision ist deterministisch).
Verifikation: jeder im Abschnitt stehende Befehl läuft unverändert gegen
den laufenden Container (lokal aus 3.1 bzw. gegen podp).
- [ ] 2.2 `PRINTER_NAME`-Wording korrigieren: README-Tabelle („Also used as
the name advertised via AirPrint/DNS-SD") und Fließtext unter
*Environment variables* sowie die Kopfkommentarzeile in
`entrypoint.sh` („advertised (DNS-SD) name") präzisieren: Der
Queue-Name ist nur die Basis, solange keine Description gesetzt ist —
`PRINTER_DESCRIPTION` (bzw. `-D`) hat Vorrang für den Anzeigenamen.
Verifikation: `grep -n 'PRINTER_NAME' README.md entrypoint.sh` zeigt an
allen drei Stellen die korrigierte Formulierung mit Description-Vorrang.
## 3. Integrationstests (podman, lokal)
- [ ] 3.1 Image bauen und ohne Kollision starten.
Verifikation: `podman build -t printserver:latest .` endet ohne Fehler;
`podman run …` erreicht „ready: CUPS on 631/tcp, mDNS on 5353/udp";
`podman logs` enthält KEINE Kollisions-Warnung (falls 631 nicht bindet:
`podman machine ssh -- sudo sysctl -w net.ipv4.ip_unprivileged_port_start=631`
wie bei der Vorgängeränderung).
- [ ] 3.2 Kollision erzeugen und Warnung nachweisen.
Verifikation: zweite Queue mit der Description des ersten Queues anlegen
(`podman exec printserver lpadmin -p dup -E -v socket://192.0.2.1:9100
-D "<identische Description>"`), danach `podman restart printserver`:
`podman logs` enthält die Warnung mit Anzeigename, beiden Queue-Namen und
Remedie, und der Start endet trotzdem mit „ready" (Warnung nicht fatal).
- [ ] 3.3 Kollision beheben → Warnung verschwindet.
Verifikation: `podman exec printserver lpadmin -p dup -D "Dup Queue"`
(eindeutige Description), `podman restart printserver`: das Log enthält
keine Kollisions-Warnung mehr und endet mit „ready".
- [ ] 3.4 Fehlendes `printers.conf` darf den Start nicht brechen.
Verifikation: `podman exec printserver sh -c 'cp /etc/cups/printers.conf /tmp/pc.bak && rm /etc/cups/printers.conf'`
danach `podman restart printserver`: der Container erreicht „ready";
anschließend Status wiederherstellen
(`podman exec … cp /tmp/pc.bak /etc/cups/printers.conf`) bzw. den
Testcontainer verwerfen.
## Workflow follow-up
- README-Abschnitt und Warnung gegen die echte Konstellation auf `podp`
prüfen: nach Deploy (Image neu bauen, Quadlet-Restart) muss die Warnung für
`laser`/`laserdrucker-im-buero` erscheinen, bis die Descriptions eindeutig
gemacht oder die redundante Queue entfernt ist — danach muss sie weg sein.
- Nach Review und grünem Durchlauf die Änderung mit `/opsx-archive`
archivieren und das Ergebnis prüfen.