Skip to content

LAN Discovery

github-actions[bot] edited this page Jul 24, 2026 · 1 revision

LAN Discovery (Bonjour/mDNS)

web.bonjour is opt-in, zero-config LAN discovery: the daemon advertises its web control API as a _cronstable._tcp service over mDNS/Bonjour, so a companion app (or a service browser) on the same network finds it without a typed URL.

What is advertised

The advert describes one listener a LAN peer can actually dial: the first bound https:// listen entry reachable from another machine, or the first such http:// one when no https listener qualifies. Loopback and unix listeners are never advertised (their ports are unreachable from any other machine), and a listener bound to one specific IPv6 address cannot be advertised (the advert's address record is IPv4-only). The advert carries:

  • the instance name: the node's hostname by default, or the map form's name: override (dots are replaced with hyphens; the label is truncated to 63 bytes);
  • the port: the actually bound TCP port of the advertised listener, so it is correct even for an ephemeral listen: http://0.0.0.0:0;
  • the address: the listener's own IP when it is bound to one specific IPv4 address, otherwise the host's primary outbound IPv4;
  • TXT records v (the daemon version) and scheme (the advertised listener's own http or https).

The advert's SRV target is a dedicated <name>-cronstable.local. hostname, so the daemon never contends with the machine's own <hostname>.local. record (which avahi or mDNSResponder may already answer for).

The advert carries no secrets: name, port, scheme, and version only. A client that discovers the daemon still needs a bearer token to read anything (see Authentication).

Configuration

Bonjour requires the discovery extra:

pip install "cronstable[discovery]"

(The extra is python-zeroconf, which is LGPL-2.1-licensed: the release binaries and Docker images bundle it together with its license notice. Any binary prints the notice with cronstable --third-party-licenses, and each GitHub Release attaches the library's source archive; the full compliance story is in LICENSING.md.)

The boolean form advertises under the hostname:

web:
  listen:
    - http://0.0.0.0:8080
  bonjour: true

The map form overrides the instance name (and enabled: false turns the advert off while keeping the block):

web:
  listen:
    - http://0.0.0.0:8080
  bonjour:
    enabled: true
    name: cron-prod-1

Validation

Both checks fail at parse time (ConfigError), so --validate-config catches them:

  • web.bonjour enabled without python-zeroconf installed: install the discovery extra or disable web.bonjour.
  • web.bonjour enabled when every web.listen entry is a unix socket: the advert needs a TCP listener to point at.

Runtime behavior

The advert follows the web app's lifecycle: it is registered while (and only while) an advertisable TCP listener is actually bound, is updated on a config reload when anything it carries changed, and is withdrawn on shutdown. A runtime mDNS failure (a registration error, no LAN-reachable listener, no non-loopback address to advertise) is logged and the advert is skipped until the next config apply; discovery is a convenience and never takes down the scheduler.

Browsing

macOS:

dns-sd -B _cronstable._tcp

Linux (avahi):

avahi-browse -r _cronstable._tcp

Related pages

Clone this wiki locally