136 lines
4.9 KiB
Python
136 lines
4.9 KiB
Python
"""
|
|
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.")
|