From 8eeda1265c4ea95d0cd34315c97886352040b65d Mon Sep 17 00:00:00 2001 From: Oier Bravo Urtasun Date: Sun, 24 May 2026 05:56:54 +0200 Subject: [PATCH] feat(mdns): optional Avahi .local responder (ENABLE_MDNS) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-contained host-network Avahi container publishes the service names over mDNS so .local works zero-config for guests (macOS/Linux native, Windows needs Bonjour). Toggle with ENABLE_MDNS=true + BASE_DOMAIN=*.local; build-stack layers docker-compose.avahi.yml in and builds the image during --prep. mDNS can't serve the air-gap upstream spoofs (non-.local) — those still need real DNS. Co-Authored-By: Claude Opus 4.7 (1M context) --- docker-compose.avahi.yml | 14 ++++++++++++++ docker/avahi/Dockerfile | 8 ++++++++ docker/avahi/entrypoint.sh | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 docker-compose.avahi.yml create mode 100644 docker/avahi/Dockerfile create mode 100755 docker/avahi/entrypoint.sh diff --git a/docker-compose.avahi.yml b/docker-compose.avahi.yml new file mode 100644 index 0000000..5875eb6 --- /dev/null +++ b/docker-compose.avahi.yml @@ -0,0 +1,14 @@ +# mDNS toggle — publish *.local service names over Avahi for zero-config guest +# resolution. Enabled when ENABLE_MDNS=true in .env (build-stack layers it in) +# and only meaningful when BASE_DOMAIN=*.local. Host networking is required for +# link-local multicast. +services: + avahi: + build: ./docker/avahi + image: ulicraft-avahi:local + container_name: avahi + restart: unless-stopped + network_mode: host + environment: + BASE_DOMAIN: ${BASE_DOMAIN} + HOST_LAN_IP: ${HOST_LAN_IP} diff --git a/docker/avahi/Dockerfile b/docker/avahi/Dockerfile new file mode 100644 index 0000000..c51798d --- /dev/null +++ b/docker/avahi/Dockerfile @@ -0,0 +1,8 @@ +# Self-contained mDNS responder: publishes the stack's service names on .local +# so guests resolve them with zero DNS/client config (macOS/Linux native; +# Windows needs Bonjour). Used only when ENABLE_MDNS=true and BASE_DOMAIN=*.local. +FROM alpine:3.20 +RUN apk add --no-cache avahi dbus +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/avahi/entrypoint.sh b/docker/avahi/entrypoint.sh new file mode 100755 index 0000000..b7774aa --- /dev/null +++ b/docker/avahi/entrypoint.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# Publish A records for the stack's service names over mDNS (.local). +# Requires host networking (mDNS is link-local multicast). One avahi-publish +# per name; if any dies the container exits so Docker restarts it. +set -eu + +: "${BASE_DOMAIN:?BASE_DOMAIN unset}" +: "${HOST_LAN_IP:?HOST_LAN_IP unset}" + +case "$BASE_DOMAIN" in + *.local) ;; + *) echo "WARN: mDNS resolves only .local — BASE_DOMAIN=$BASE_DOMAIN will NOT be discoverable via avahi. Set BASE_DOMAIN=*.local to use mDNS." >&2 ;; +esac + +mkdir -p /run/dbus +rm -f /run/dbus/pid /run/dbus/system_bus_socket 2>/dev/null || true +dbus-daemon --system +for _ in 1 2 3 4 5; do [ -S /run/dbus/system_bus_socket ] && break; sleep 1; done + +avahi-daemon --daemonize --no-chroot +sleep 2 + +pids="" +for n in "$BASE_DOMAIN" "auth.$BASE_DOMAIN" "packwiz.$BASE_DOMAIN" "mc.$BASE_DOMAIN"; do + echo "mDNS publish: $n -> $HOST_LAN_IP" + avahi-publish -a "$n" "$HOST_LAN_IP" & + pids="$pids $!" +done + +# Block on the publishers; exit (→ restart) if one drops. +# shellcheck disable=SC2086 +wait -n $pids 2>/dev/null || wait