Development

This commit is contained in:
Matthew Grotke 2026-06-05 01:48:27 -04:00
parent 205d6889df
commit 58ab569e42
27 changed files with 2894 additions and 2605 deletions

136
routlin/mod_avahi.py Normal file
View file

@ -0,0 +1,136 @@
"""
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.")