diff --git a/reticulum-daemon/reticulum_daemon.py b/reticulum-daemon/reticulum_daemon.py index 2fd17985..3fb15eef 100644 --- a/reticulum-daemon/reticulum_daemon.py +++ b/reticulum-daemon/reticulum_daemon.py @@ -502,7 +502,32 @@ def _parse_args(argv): return p.parse_args(argv) +def _install_parent_death_signal() -> None: + """Die when our parent process does. + + The daemon ships as a PyInstaller one-file binary: our direct parent is the + bootloader, and the Rust supervisor (mesh/reticulum.rs) stops us by SIGKILL- + ing that bootloader. SIGKILL can't be forwarded, so without this the Python + child is orphaned and keeps holding the RNode serial port — which piles up + stale daemons that jam the radio (observed: 9 instances on one node). Asking + the kernel to send us SIGTERM on parent death lets our existing SIGTERM + handler shut down cleanly and free the port. Linux-only; no-op elsewhere. + """ + if sys.platform != "linux": + return + try: + import ctypes + + PR_SET_PDEATHSIG = 1 + ctypes.CDLL("libc.so.6", use_errno=True).prctl( + PR_SET_PDEATHSIG, signal.SIGTERM + ) + except Exception: + pass # best-effort; never block startup on this + + def main(argv=None) -> int: + _install_parent_death_signal() args = _parse_args(argv if argv is not None else sys.argv[1:]) if args.check: