98 lines
2.9 KiB
Python
98 lines
2.9 KiB
Python
"""Publish this device on the LAN: hostname + optional services.
|
|
|
|
Config example (config/services.example.yaml or JSON):
|
|
hostname: movil
|
|
services:
|
|
- {type: _ssh._tcp, port: 8022, name: "Termux SSH"}
|
|
- {type: _smb._tcp, port: 4450, name: "Termux SMB"}
|
|
"""
|
|
import socket
|
|
import time
|
|
from pathlib import Path
|
|
|
|
from zeroconf import Zeroconf, ServiceInfo
|
|
|
|
|
|
def _local_ipv4() -> str:
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
try:
|
|
s.connect(("8.8.8.8", 80))
|
|
return s.getsockname()[0]
|
|
except OSError:
|
|
return "127.0.0.1"
|
|
finally:
|
|
s.close()
|
|
|
|
|
|
def _load_config(path: str | None) -> dict:
|
|
if not path:
|
|
return {"hostname": "movil", "services": []}
|
|
p = Path(path).expanduser()
|
|
if not p.exists():
|
|
return {"hostname": "movil", "services": []}
|
|
text = p.read_text()
|
|
if p.suffix in (".yml", ".yaml"):
|
|
try:
|
|
import yaml # type: ignore
|
|
return yaml.safe_load(text) or {}
|
|
except ImportError:
|
|
print("warning: PyYAML not installed, using minimal parser")
|
|
import json
|
|
try:
|
|
return json.loads(text)
|
|
except Exception:
|
|
return {"hostname": "movil", "services": []}
|
|
|
|
|
|
def run(config_path: str | None = None,
|
|
hostname: str | None = None,
|
|
verbose: bool = True) -> None:
|
|
"""Publish hostname + services and block forever."""
|
|
cfg = _load_config(config_path)
|
|
host = hostname or cfg.get("hostname", "movil")
|
|
ip = _local_ipv4()
|
|
zc = Zeroconf()
|
|
infos: list[ServiceInfo] = []
|
|
# Register hostname via _workstation._tcp so the device shows up in browsers
|
|
ws = ServiceInfo(
|
|
type_="_workstation._tcp.local.",
|
|
name=f"{host} [{ip.replace('.', '-')}]._workstation._tcp.local.",
|
|
addresses=[socket.inet_aton(ip)],
|
|
port=9,
|
|
server=f"{host}.local.",
|
|
)
|
|
zc.register_service(ws)
|
|
infos.append(ws)
|
|
for svc in (cfg.get("services") or []):
|
|
try:
|
|
t = svc["type"].rstrip(".") + ".local."
|
|
n = f'{svc.get("name", host)}.{t}'
|
|
info = ServiceInfo(
|
|
type_=t,
|
|
name=n,
|
|
addresses=[socket.inet_aton(ip)],
|
|
port=int(svc["port"]),
|
|
server=f"{host}.local.",
|
|
properties=svc.get("txt", {}),
|
|
)
|
|
zc.register_service(info)
|
|
infos.append(info)
|
|
except Exception as e:
|
|
print(f"warning: could not register {svc}: {e}")
|
|
if verbose:
|
|
print(f"[mdns] published {host}.local ({ip}), {len(infos)} record(s). Ctrl+C to stop.")
|
|
try:
|
|
while True:
|
|
time.sleep(60)
|
|
except KeyboardInterrupt:
|
|
pass
|
|
finally:
|
|
for i in infos:
|
|
try:
|
|
zc.unregister_service(i)
|
|
except Exception:
|
|
pass
|
|
zc.close()
|
|
if verbose:
|
|
print("[mdns] stopped, goodbye packets sent.")
|