78 lines
2.3 KiB
Python
78 lines
2.3 KiB
Python
|
|
"""Sync discovered mDNS hosts into the system hosts file.
|
||
|
|
|
||
|
|
Writes a managed block so native tools (ping, curl, kubectl) resolve `.local`
|
||
|
|
names without the `resolve` wrapper. Only IPv4, only the managed block is
|
||
|
|
touched — the rest of the file is preserved.
|
||
|
|
"""
|
||
|
|
import os
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
from . import browse as _browse
|
||
|
|
from ._pick import pick_best
|
||
|
|
|
||
|
|
MARK_BEGIN = "# >>> mdns-termux managed >>>"
|
||
|
|
MARK_END = "# <<< mdns-termux managed <<<"
|
||
|
|
|
||
|
|
|
||
|
|
def hosts_path() -> Path:
|
||
|
|
prefix = os.environ.get("PREFIX", "")
|
||
|
|
if prefix and "com.termux" in prefix:
|
||
|
|
return Path(prefix) / "etc" / "hosts"
|
||
|
|
return Path("/etc/hosts")
|
||
|
|
|
||
|
|
|
||
|
|
def collect(duration: float = 3.0) -> dict[str, str]:
|
||
|
|
"""Return {hostname.local: best_ipv4} from all advertised services."""
|
||
|
|
out: dict[str, str] = {}
|
||
|
|
for it in _browse.browse(None, duration):
|
||
|
|
server = (it.get("server") or "").rstrip(".")
|
||
|
|
v4 = [a for a in (it.get("addresses") or []) if ":" not in a]
|
||
|
|
if not server or not v4:
|
||
|
|
continue
|
||
|
|
best = pick_best(v4, family="v4")
|
||
|
|
if best and server not in out:
|
||
|
|
out[server] = best
|
||
|
|
return out
|
||
|
|
|
||
|
|
|
||
|
|
def _render_block(mapping: dict[str, str]) -> str:
|
||
|
|
lines = [MARK_BEGIN]
|
||
|
|
for host in sorted(mapping):
|
||
|
|
short = host[:-6] if host.endswith(".local") else host
|
||
|
|
lines.append(f"{mapping[host]}\t{host} {short}")
|
||
|
|
lines.append(MARK_END)
|
||
|
|
return "\n".join(lines)
|
||
|
|
|
||
|
|
|
||
|
|
def _strip_block(text: str) -> str:
|
||
|
|
out, skip = [], False
|
||
|
|
for line in text.splitlines():
|
||
|
|
if line.strip() == MARK_BEGIN:
|
||
|
|
skip = True
|
||
|
|
continue
|
||
|
|
if line.strip() == MARK_END:
|
||
|
|
skip = False
|
||
|
|
continue
|
||
|
|
if not skip:
|
||
|
|
out.append(line)
|
||
|
|
return "\n".join(out).rstrip("\n")
|
||
|
|
|
||
|
|
|
||
|
|
def sync(duration: float = 3.0, dry_run: bool = False) -> int:
|
||
|
|
mapping = collect(duration)
|
||
|
|
if not mapping:
|
||
|
|
print("no hosts discovered on the LAN")
|
||
|
|
return 1
|
||
|
|
block = _render_block(mapping)
|
||
|
|
if dry_run:
|
||
|
|
print(block)
|
||
|
|
return 0
|
||
|
|
path = hosts_path()
|
||
|
|
original = path.read_text() if path.exists() else "127.0.0.1\tlocalhost\n"
|
||
|
|
body = _strip_block(original)
|
||
|
|
path.write_text(f"{body}\n{block}\n")
|
||
|
|
print(f"wrote {len(mapping)} host(s) to {path}")
|
||
|
|
for host, ip in sorted(mapping.items()):
|
||
|
|
print(f" {ip}\t{host}")
|
||
|
|
return 0
|