From 46cff2ddf23572311450e6c921dcf11a564d278c Mon Sep 17 00:00:00 2001 From: Naomi Calabretta Date: Wed, 24 Jun 2026 17:53:59 +0200 Subject: [PATCH 01/12] feat: add service manager check logic --- .../localplatform/localplatformlinux.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py index 21993e4c..84aec6ae 100644 --- a/backend/decky_loader/localplatform/localplatformlinux.py +++ b/backend/decky_loader/localplatform/localplatformlinux.py @@ -8,6 +8,8 @@ logger = logging.getLogger("localplatform") +service_manager = _get_service_manager() + # subprocess._ENV ENV = Mapping[str, str] ProcessIO = int | IO[Any] | None @@ -16,6 +18,19 @@ async def run(args: list[str], stdin: ProcessIO = DEVNULL, stdout: ProcessIO = P proc_stdout, proc_stderr = await proc.communicate() return (proc, proc_stdout, proc_stderr) +def _get_service_manager(): + try: + link = os.path.realpath("/sbin/init") + service = os.path.basename(link) + match service: + case "openrc-init": + return "openrc" + case _: + return service + except: + # As it's the most common, default to Systemd + return "systemd" + # Get the user id hosting the plugin loader def _get_user_id() -> int: return pwd.getpwnam(_get_user()).pw_uid From 086b770572582395a857ffe703d592d47a1c8aa6 Mon Sep 17 00:00:00 2001 From: Naomi Calabretta Date: Wed, 24 Jun 2026 18:05:24 +0200 Subject: [PATCH 02/12] feat: add openrc commands --- .../localplatform/localplatformlinux.py | 60 +++++++++++++++---- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py index 84aec6ae..d730098d 100644 --- a/backend/decky_loader/localplatform/localplatformlinux.py +++ b/backend/decky_loader/localplatform/localplatformlinux.py @@ -22,7 +22,7 @@ def _get_service_manager(): try: link = os.path.realpath("/sbin/init") service = os.path.basename(link) - match service: + match service_manager: case "openrc-init": return "openrc" case _: @@ -153,16 +153,36 @@ def setuid(user : UserType = UserType.HOST_USER): raise Exception("Unknown user type") async def service_active(service_name : str) -> bool: - res, _, _ = await run(["systemctl", "is-active", service_name], stdout=DEVNULL, stderr=DEVNULL) + match service_manager: + case "systemd": + cmd = ["systemctl", "is-active", service_name] + case "openrc": + cmd = ["rc-service"] + if _get_effective_user_id() != 0: cmd.append("--user") + cmd.append(service_name) + cmd.append("status") + case _: + return True # Stub unsupported services + + res, _, _ = await run(cmd, stdout=DEVNULL, stderr=DEVNULL) return res.returncode == 0 async def service_restart(service_name : str, block : bool = True) -> bool: - await run(["systemctl", "daemon-reload"]) - logger.info("Systemd reload done.") - cmd = ["systemctl", "restart", service_name] - - if not block: - cmd.append("--no-block") + match service_manager: + case "systemd": + await run(["systemctl", "daemon-reload"]) + logger.info("Systemd reload done.") + cmd = ["systemctl", "restart", service_name] + + if not block: + cmd.append("--no-block") + case "openrc": + cmd = ["rc-service"] + if _get_effective_user_id() != 0: cmd.append("--user") + cmd.append(service_name) + cmd.append("restart") + case _: + return True # Stub unsupported services res, _, _ = await run(cmd, stdout=PIPE, stderr=STDOUT) return res.returncode == 0 @@ -172,7 +192,17 @@ async def service_stop(service_name : str) -> bool: # Service isn't running. pretend we stopped it return True - cmd = ["systemctl", "stop", service_name] + match service_manager: + case "systemd": + cmd = ["systemctl", "stop", service_name] + case "openrc": + cmd = ["rc-service"] + if _get_effective_user_id() != 0: cmd.append("--user") + cmd.append(service_name) + cmd.append("stop") + case _: + return True # Stub unsupported services + res, _, _ = await run(cmd, stdout=PIPE, stderr=STDOUT) return res.returncode == 0 @@ -181,7 +211,17 @@ async def service_start(service_name : str) -> bool: # Service is running. pretend we started it return True - cmd = ["systemctl", "start", service_name] + match service_manager: + case "systemd": + cmd = ["systemctl", "start", service_name] + case "openrc": + cmd = ["rc-service"] + if _get_effective_user_id() != 0: cmd.append("--user") + cmd.append(service_name) + cmd.append("start") + case _: + return True # Stub unsupported services + res, _, _ = await run(cmd, stdout=PIPE, stderr=STDOUT) return res.returncode == 0 From 3b56f5254a7cba87273be1b600afdc4c37770170 Mon Sep 17 00:00:00 2001 From: Naomi Calabretta Date: Wed, 24 Jun 2026 18:20:31 +0200 Subject: [PATCH 03/12] feat: add openrc scripts --- dist/plugin_loader-prerelease.openrc | 13 +++++++++++++ dist/plugin_loader-release.openrc | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 dist/plugin_loader-prerelease.openrc create mode 100644 dist/plugin_loader-release.openrc diff --git a/dist/plugin_loader-prerelease.openrc b/dist/plugin_loader-prerelease.openrc new file mode 100644 index 00000000..aa649043 --- /dev/null +++ b/dist/plugin_loader-prerelease.openrc @@ -0,0 +1,13 @@ +#!/sbin/openrc-run + +export UNPRIVILEGED_PATH="${HOMEBREW_FOLDER}" +export PRIVILEGED_PATH="${HOMEBREW_FOLDER}" +export LOG_LEVEL="DEBUG" + +supervisor=supervise-daemon +directory="${HOMEBREW_FOLDER}/services" +command="${HOMEBREW_FOLDER}/services/PluginLoader" + +depend() { + need net +} diff --git a/dist/plugin_loader-release.openrc b/dist/plugin_loader-release.openrc new file mode 100644 index 00000000..5190a8b9 --- /dev/null +++ b/dist/plugin_loader-release.openrc @@ -0,0 +1,13 @@ +#!/sbin/openrc-run + +export UNPRIVILEGED_PATH="${HOMEBREW_FOLDER}" +export PRIVILEGED_PATH="${HOMEBREW_FOLDER}" +export LOG_LEVEL="INFO" + +supervisor=supervise-daemon +directory="${HOMEBREW_FOLDER}/services" +command="${HOMEBREW_FOLDER}/services/PluginLoader" + +depend() { + need net +} From 0ff626f8ca38ad9598384697e6541a5682d85e59 Mon Sep 17 00:00:00 2001 From: Naomi Calabretta Date: Wed, 24 Jun 2026 18:33:07 +0200 Subject: [PATCH 04/12] chore: add type --- backend/decky_loader/localplatform/localplatformlinux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py index d730098d..87e92a26 100644 --- a/backend/decky_loader/localplatform/localplatformlinux.py +++ b/backend/decky_loader/localplatform/localplatformlinux.py @@ -18,7 +18,7 @@ async def run(args: list[str], stdin: ProcessIO = DEVNULL, stdout: ProcessIO = P proc_stdout, proc_stderr = await proc.communicate() return (proc, proc_stdout, proc_stderr) -def _get_service_manager(): +def _get_service_manager() -> str: try: link = os.path.realpath("/sbin/init") service = os.path.basename(link) From 552becad37f06aa79a8d7971c3df71705c3ae399 Mon Sep 17 00:00:00 2001 From: Naomi Calabretta Date: Thu, 25 Jun 2026 14:16:53 +0200 Subject: [PATCH 05/12] chore: add an env variable to override service manager impl on Linux --- backend/decky_loader/localplatform/localplatformlinux.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py index 87e92a26..bfb05e60 100644 --- a/backend/decky_loader/localplatform/localplatformlinux.py +++ b/backend/decky_loader/localplatform/localplatformlinux.py @@ -20,6 +20,7 @@ async def run(args: list[str], stdin: ProcessIO = DEVNULL, stdout: ProcessIO = P def _get_service_manager() -> str: try: + if os.getenv("LINUX_SERVICE_MANAGER"): return os.getenv("LINUX_SERVICE_MANAGER") link = os.path.realpath("/sbin/init") service = os.path.basename(link) match service_manager: From f6c8ffe8efb48a491fb0703a2f4e7337c5d70ece Mon Sep 17 00:00:00 2001 From: Naomi Calabretta Date: Thu, 25 Jun 2026 19:43:33 +0200 Subject: [PATCH 06/12] chore: learn the hard way that nothing in python is hoisted --- .../localplatform/localplatformlinux.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py index bfb05e60..20eab2a2 100644 --- a/backend/decky_loader/localplatform/localplatformlinux.py +++ b/backend/decky_loader/localplatform/localplatformlinux.py @@ -8,16 +8,6 @@ logger = logging.getLogger("localplatform") -service_manager = _get_service_manager() - -# subprocess._ENV -ENV = Mapping[str, str] -ProcessIO = int | IO[Any] | None -async def run(args: list[str], stdin: ProcessIO = DEVNULL, stdout: ProcessIO = PIPE, stderr: ProcessIO = PIPE, env: ENV | None = {"LD_LIBRARY_PATH": ""}) -> tuple[Process, bytes | None, bytes | None]: - proc = await create_subprocess_exec(args[0], *(args[1:]), stdin=stdin, stdout=stdout, stderr=stderr, env=env) - proc_stdout, proc_stderr = await proc.communicate() - return (proc, proc_stdout, proc_stderr) - def _get_service_manager() -> str: try: if os.getenv("LINUX_SERVICE_MANAGER"): return os.getenv("LINUX_SERVICE_MANAGER") @@ -32,6 +22,16 @@ def _get_service_manager() -> str: # As it's the most common, default to Systemd return "systemd" +service_manager = _get_service_manager() + +# subprocess._ENV +ENV = Mapping[str, str] +ProcessIO = int | IO[Any] | None +async def run(args: list[str], stdin: ProcessIO = DEVNULL, stdout: ProcessIO = PIPE, stderr: ProcessIO = PIPE, env: ENV | None = {"LD_LIBRARY_PATH": ""}) -> tuple[Process, bytes | None, bytes | None]: + proc = await create_subprocess_exec(args[0], *(args[1:]), stdin=stdin, stdout=stdout, stderr=stderr, env=env) + proc_stdout, proc_stderr = await proc.communicate() + return (proc, proc_stdout, proc_stderr) + # Get the user id hosting the plugin loader def _get_user_id() -> int: return pwd.getpwnam(_get_user()).pw_uid From 512ed453d7acdf45ada2ca7bee167f24c42c6506 Mon Sep 17 00:00:00 2001 From: Naomi Calabretta Date: Thu, 25 Jun 2026 19:49:35 +0200 Subject: [PATCH 07/12] fix: replaced too many service var names --- backend/decky_loader/localplatform/localplatformlinux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py index 20eab2a2..4aa4b47b 100644 --- a/backend/decky_loader/localplatform/localplatformlinux.py +++ b/backend/decky_loader/localplatform/localplatformlinux.py @@ -13,7 +13,7 @@ def _get_service_manager() -> str: if os.getenv("LINUX_SERVICE_MANAGER"): return os.getenv("LINUX_SERVICE_MANAGER") link = os.path.realpath("/sbin/init") service = os.path.basename(link) - match service_manager: + match service: case "openrc-init": return "openrc" case _: From b9ecbcc6557801bd9d11843d1a2db645eb736960 Mon Sep 17 00:00:00 2001 From: Naomi Calabretta Date: Thu, 25 Jun 2026 19:59:58 +0200 Subject: [PATCH 08/12] chore: handle quirky init stuff --- backend/decky_loader/localplatform/localplatformlinux.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py index 4aa4b47b..363afc8c 100644 --- a/backend/decky_loader/localplatform/localplatformlinux.py +++ b/backend/decky_loader/localplatform/localplatformlinux.py @@ -17,7 +17,9 @@ def _get_service_manager() -> str: case "openrc-init": return "openrc" case _: - return service + if service != None and service != "init": return service + # Non-supervised or highly non-standard Linux installation + return "no_init" except: # As it's the most common, default to Systemd return "systemd" From 91f284e0a0181ba0a8770c6811ea041a5f4dabdb Mon Sep 17 00:00:00 2001 From: Naomi Calabretta Date: Thu, 25 Jun 2026 20:01:39 +0200 Subject: [PATCH 09/12] chore: move logic, raise exception when necessary --- backend/decky_loader/localplatform/localplatformlinux.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py index 363afc8c..5f30053e 100644 --- a/backend/decky_loader/localplatform/localplatformlinux.py +++ b/backend/decky_loader/localplatform/localplatformlinux.py @@ -13,11 +13,12 @@ def _get_service_manager() -> str: if os.getenv("LINUX_SERVICE_MANAGER"): return os.getenv("LINUX_SERVICE_MANAGER") link = os.path.realpath("/sbin/init") service = os.path.basename(link) + if service == None: raise Exception("This Linux installation doesn't have /sbin/init.") match service: case "openrc-init": return "openrc" case _: - if service != None and service != "init": return service + if service != "init": return service # Non-supervised or highly non-standard Linux installation return "no_init" except: From 9081ac0c36eb766dc88fde2731ebff0a25ff7b43 Mon Sep 17 00:00:00 2001 From: Naomi Calabretta Date: Thu, 25 Jun 2026 20:03:28 +0200 Subject: [PATCH 10/12] fix: lint --- backend/decky_loader/localplatform/localplatformlinux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py index 5f30053e..c3c8aed9 100644 --- a/backend/decky_loader/localplatform/localplatformlinux.py +++ b/backend/decky_loader/localplatform/localplatformlinux.py @@ -13,7 +13,7 @@ def _get_service_manager() -> str: if os.getenv("LINUX_SERVICE_MANAGER"): return os.getenv("LINUX_SERVICE_MANAGER") link = os.path.realpath("/sbin/init") service = os.path.basename(link) - if service == None: raise Exception("This Linux installation doesn't have /sbin/init.") + if not service: raise Exception("This Linux installation doesn't have /sbin/init.") match service: case "openrc-init": return "openrc" From 4146ccced8b5099267874f16f729005611e7dbda Mon Sep 17 00:00:00 2001 From: Naomi Calabretta Date: Thu, 25 Jun 2026 20:04:24 +0200 Subject: [PATCH 11/12] fix: for my stupidity, i was checking against the wrong variable --- backend/decky_loader/localplatform/localplatformlinux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py index c3c8aed9..ae545115 100644 --- a/backend/decky_loader/localplatform/localplatformlinux.py +++ b/backend/decky_loader/localplatform/localplatformlinux.py @@ -12,8 +12,8 @@ def _get_service_manager() -> str: try: if os.getenv("LINUX_SERVICE_MANAGER"): return os.getenv("LINUX_SERVICE_MANAGER") link = os.path.realpath("/sbin/init") + if not link: raise Exception("This Linux installation doesn't have /sbin/init.") service = os.path.basename(link) - if not service: raise Exception("This Linux installation doesn't have /sbin/init.") match service: case "openrc-init": return "openrc" From 8c2be66cf1a6003afa9dc6db94003ef91229ca73 Mon Sep 17 00:00:00 2001 From: Naomi Calabretta Date: Thu, 25 Jun 2026 20:13:13 +0200 Subject: [PATCH 12/12] fix: typecheck env override --- backend/decky_loader/localplatform/localplatformlinux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py index ae545115..2e2f3a15 100644 --- a/backend/decky_loader/localplatform/localplatformlinux.py +++ b/backend/decky_loader/localplatform/localplatformlinux.py @@ -10,7 +10,7 @@ def _get_service_manager() -> str: try: - if os.getenv("LINUX_SERVICE_MANAGER"): return os.getenv("LINUX_SERVICE_MANAGER") + if isinstance(os.getenv("LINUX_SERVICE_MANAGER"), str): return os.getenv("LINUX_SERVICE_MANAGER") link = os.path.realpath("/sbin/init") if not link: raise Exception("This Linux installation doesn't have /sbin/init.") service = os.path.basename(link)