""" mod_avahi.py -- Avahi mDNS reflector configuration and management. Patches avahi-daemon.conf with the correct interface list and manages the avahi-daemon service lifecycle. """ import re import shutil import subprocess from pathlib import Path import mod_shared as shared import mod_validation as validation AVAHI_CONF_FILE = Path("/etc/avahi/avahi-daemon.conf") # =================================================================== # State helpers # =================================================================== def avahi_enabled(data): """Return True if at least one non-WireGuard VLAN has mdns_reflection enabled.""" return any(v.get("mdns_reflection") is True for v in data.get("vlans", []) if not validation.is_wg(v)) def avahi_interfaces(data): """Return list of interface names for VLANs with mdns_reflection enabled.""" return [ validation.derive_interface(v, data) for v in data.get("vlans", []) if v.get("mdns_reflection") is True and not validation.is_wg(v) ] # =================================================================== # Config generation # =================================================================== def build_avahi_conf(data): """Patch avahi-daemon.conf directives needed for cross-VLAN mDNS reflection. Reads the existing file (default or previously patched) and modifies only the specific directives we need, leaving everything else untouched. Returns the patched config as a string. Caller is responsible for ensuring the file exists before calling (apply_avahi guards this). """ ifaces = avahi_interfaces(data) content = AVAHI_CONF_FILE.read_text() def set_directive(text, directive, value): """Enable and set a directive, whether it is commented out or already set.""" pattern = re.compile( rf"^#?\s*{re.escape(directive)}\s*=.*$", re.MULTILINE ) replacement = f"{directive}={value}" if pattern.search(text): return pattern.sub(replacement, text) return text + f"\n{replacement}\n" content = set_directive(content, "use-ipv6", "no") content = set_directive(content, "disallow-other-stacks", "yes") content = set_directive(content, "allow-interfaces", ",".join(ifaces)) content = set_directive(content, "enable-reflector", "yes") content = set_directive(content, "disable-publishing", "yes") return content # =================================================================== # Apply # =================================================================== def apply_avahi(data): """Write avahi-daemon.conf and ensure service is running.""" if not shutil.which("avahi-daemon"): print("avahi-daemon is not installed.") print(" -> Run: sudo python3 install.py") return ifaces = avahi_interfaces(data) if len(ifaces) < 2: print("mDNS reflection requires at least two VLANs in reflect_vlans. Skipping.") return if not AVAHI_CONF_FILE.exists(): print(f"WARNING: {AVAHI_CONF_FILE} not found. Run: sudo python3 install.py") return content = build_avahi_conf(data) existing = AVAHI_CONF_FILE.read_text() changed = existing != content if changed: AVAHI_CONF_FILE.write_text(content) print(f"Written: {AVAHI_CONF_FILE}") print(f" Reflecting mDNS across: {', '.join(ifaces)}") else: print(f"Unchanged: {AVAHI_CONF_FILE}") svc = "avahi-daemon" state = subprocess.run( ["systemctl", "is-active", svc], capture_output=True, text=True ).stdout.strip() if state == "active": if changed: result = subprocess.run(["systemctl", "restart", svc], capture_output=True, text=True) if result.returncode == 0: print("avahi-daemon restarted.") else: shared.service_warning("restart", "avahi-daemon", result.stderr) else: print("avahi-daemon: running, config unchanged.") else: subprocess.run(["systemctl", "enable", svc], capture_output=True, text=True) result = subprocess.run(["systemctl", "start", svc], capture_output=True, text=True) if result.returncode == 0: print("avahi-daemon started.") else: shared.service_warning("start", "avahi-daemon", result.stderr) def disable_avahi(): """Stop and disable avahi-daemon.""" result = subprocess.run( ["systemctl", "is-active", "avahi-daemon"], capture_output=True, text=True ) if result.stdout.strip() == "active": subprocess.run(["systemctl", "disable", "--now", "avahi-daemon"], capture_output=True, text=True) print("avahi-daemon stopped and disabled.") else: print("avahi-daemon: not running, skipping.")