diff options
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/platform.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/platform.py | 88 |
1 files changed, 88 insertions, 0 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/platform.py b/packages/meshbay-node/src/meshbay_node/platform.py index 351289f..b59418f 100644 --- a/packages/meshbay-node/src/meshbay_node/platform.py +++ b/packages/meshbay-node/src/meshbay_node/platform.py @@ -3,6 +3,7 @@ import asyncio import os import shutil +import subprocess import sys from pathlib import Path @@ -109,3 +110,90 @@ def ffmpeg_cmd() -> str: def ffprobe_cmd() -> str: return _ffprobe_path + + +# ── Autostart (Windows) ────────────────────────────────────────────────────── +# +# The Windows stand-in for the Linux `systemctl --user` unit. Task Scheduler +# would be nicer (retry semantics), but a logon-triggered task needs elevation +# to create — and this must work for an ordinary user with no admin rights. +# So: a `.vbs` launcher in the per-user Startup folder. wscript runs it hidden +# (Run(..., 0, ...)) at every sign-in; no console window, no admin, no +# third-party dependency. + +TASK_NAME = "MeshBay Node" # the name the Electron client shows + + +def autostart_supported() -> bool: + return sys.platform == "win32" + + +def _startup_vbs() -> Path: + base = os.environ.get("APPDATA") or str(Path.home() / "AppData" / "Roaming") + return (Path(base) / "Microsoft" / "Windows" / "Start Menu" / "Programs" + / "Startup" / "MeshBay Node.vbs") + + +def _node_exe() -> str | None: + """Best guess at the meshbay-node launcher: PATH first, then next to the + interpreter (a venv's Scripts/ dir, or a bundled runtime), then argv[0].""" + found = shutil.which("meshbay-node") + if found: + return found + for cand in (Path(sys.executable).parent / "meshbay-node.exe", + Path(sys.argv[0])): + if cand.name.lower().startswith("meshbay-node") and cand.exists(): + return str(cand.resolve()) + return None + + +def autostart_status() -> dict: + """{'installed': bool, 'state': str}. 'state' is left empty — there is no + Task Scheduler to ask 'is it running'; the Node page probes the daemon.""" + if not autostart_supported(): + return {"installed": False, "state": ""} + return {"installed": _startup_vbs().exists(), "state": ""} + + +def autostart_install(exe: str | None = None) -> None: + """Write the Startup-folder launcher. Raises RuntimeError on failure.""" + if not autostart_supported(): + raise RuntimeError("autostart is Windows-only") + exe = exe or _node_exe() + if not exe: + raise RuntimeError( + "cannot locate the meshbay-node launcher — pass its path, or run " + "this from where meshbay-node is on PATH") + vbs = _startup_vbs() + vbs.parent.mkdir(parents=True, exist_ok=True) + # Chr(34) is a literal " — wraps the path so a space in it doesn't split the + # command. 0 = hidden window, False = don't wait. (A Windows path cannot + # itself contain ", so no further escaping is needed.) + vbs.write_text( + f'CreateObject("WScript.Shell").Run Chr(34) & "{exe}" & Chr(34), 0, False\n', + encoding="utf-8", newline="\r\n") + + +def autostart_remove() -> None: + """Delete the Startup-folder launcher if present.""" + if autostart_supported(): + _startup_vbs().unlink(missing_ok=True) + + +def autostart_run() -> None: + """Start the daemon now, detached and windowless. Raises RuntimeError if + the launcher cannot be located.""" + if not autostart_supported(): + raise RuntimeError("autostart is Windows-only") + exe = _node_exe() + if not exe: + raise RuntimeError("cannot locate the meshbay-node launcher") + subprocess.Popen([exe], creationflags=0x00000008 | 0x08000000, # DETACHED | NO_WINDOW + close_fds=True) + + +def autostart_end() -> None: + """Stop any running daemon (hard: there is no CTRL_CLOSE handler yet).""" + if autostart_supported(): + subprocess.run(["taskkill", "/IM", "meshbay-node.exe", "/F"], + capture_output=True) |