summaryrefslogtreecommitdiff
path: root/backend/decky_loader
diff options
context:
space:
mode:
authorAAGaming <aagaming@riseup.net>2025-07-28 20:58:59 -0400
committerGitHub <noreply@github.com>2025-07-28 20:58:59 -0400
commit8f41eb93ef80bfbf3851ce8a82ea0f88c87e6c68 (patch)
tree6cbfdf1843dc0aba959e9b76201df149a71964fc /backend/decky_loader
parent670ae7d8a7d8fc8efc28ce403865177ebf0b715e (diff)
downloaddecky-loader-aca3b3d2653abb30dddc101f28e50199db3656ab.tar.gz
decky-loader-aca3b3d2653abb30dddc101f28e50199db3656ab.zip
Merge commit from forkv3.1.10
* fix incorrect permissions on plugin directories * chown plugin dirs too * fix the stupid * cleanup useless comments
Diffstat (limited to 'backend/decky_loader')
-rw-r--r--backend/decky_loader/browser.py35
-rw-r--r--backend/decky_loader/enums.py5
-rw-r--r--backend/decky_loader/helpers.py3
-rw-r--r--backend/decky_loader/localplatform/localplatformlinux.py17
-rw-r--r--backend/decky_loader/localplatform/localplatformwin.py5
-rw-r--r--backend/decky_loader/main.py2
-rw-r--r--backend/decky_loader/plugin/plugin.py23
-rw-r--r--backend/decky_loader/plugin/sandboxed_plugin.py8
-rw-r--r--backend/decky_loader/settings.py6
-rw-r--r--backend/decky_loader/wsrouter.py4
10 files changed, 69 insertions, 39 deletions
diff --git a/backend/decky_loader/browser.py b/backend/decky_loader/browser.py
index 4a63f6ec..975a917a 100644
--- a/backend/decky_loader/browser.py
+++ b/backend/decky_loader/browser.py
@@ -18,9 +18,10 @@ from enum import IntEnum
from typing import Dict, List, TypedDict
# Local modules
-from .localplatform.localplatform import chown, chmod
+from .localplatform.localplatform import chown, chmod, get_chown_plugin_path
from .loader import Loader, Plugins
from .helpers import get_ssl_context, download_remote_binary_to_path
+from .enums import UserType
from .settings import SettingsManager
logger = getLogger("Browser")
@@ -60,13 +61,6 @@ class PluginBrowser:
return False
zip_file = ZipFile(zip)
zip_file.extractall(self.plugin_path)
- plugin_folder = self.find_plugin_folder(name)
- assert plugin_folder is not None
- plugin_dir = path.join(self.plugin_path, plugin_folder)
-
- if not chown(plugin_dir) or not chmod(plugin_dir, 555):
- logger.error(f"chown/chmod exited with a non-zero exit code")
- return False
return True
async def _download_remote_binaries_for_plugin_with_name(self, pluginBasePath: str):
@@ -101,8 +95,6 @@ class PluginBrowser:
rv = False
raise Exception(f"Error Downloading Remote Binary {binName}@{binURL} with hash {binHash} to {path.join(pluginBinPath, binName)}")
- chown(self.plugin_path)
- chmod(pluginBasePath, 555)
else:
rv = True
logger.info(f"No Remote Binaries to Download")
@@ -124,6 +116,25 @@ class PluginBrowser:
return folder
except:
logger.debug(f"skipping {folder}")
+
+ def set_plugin_dir_permissions(self, plugin_dir: str) -> bool:
+ plugin_json_path = path.join(plugin_dir, 'plugin.json')
+ logger.debug(f"Checking plugin.json at {plugin_json_path}")
+
+ root_plugin = False
+
+ if access(plugin_json_path, R_OK):
+ with open(plugin_json_path, "r", encoding="utf-8") as f:
+ plugin_json = json.load(f)
+ if "flags" in plugin_json and "root" in plugin_json["flags"]:
+ root_plugin = True
+
+ logger.debug("root_plugin %d, dir %s", root_plugin, plugin_dir)
+ if get_chown_plugin_path():
+ return chown(plugin_dir, UserType.EFFECTIVE_USER if root_plugin else UserType.HOST_USER, True) and chown(plugin_dir, UserType.EFFECTIVE_USER, False) and chmod(plugin_dir, 755) and chown(plugin_json_path, UserType.EFFECTIVE_USER, False) and chmod(plugin_json_path, 755)
+ else:
+ logger.debug("chown disabled by environment")
+ return True
async def uninstall_plugin(self, name: str):
if self.loader.watcher:
@@ -266,6 +277,7 @@ class PluginBrowser:
plugin_dir = path.join(self.plugin_path, plugin_folder)
await self.loader.ws.emit("loader/plugin_download_info", 95, "Store.download_progress_info.download_remote")
ret = await self._download_remote_binaries_for_plugin_with_name(plugin_dir)
+ chown_ret = self.set_plugin_dir_permissions(plugin_dir)
if ret:
logger.info(f"Installed {name} (Version: {version})")
if name in self.loader.plugins:
@@ -278,6 +290,9 @@ class PluginBrowser:
self.settings.setSetting("pluginOrder", current_plugin_order)
logger.debug("Plugin %s was added to the pluginOrder setting", name)
await self.loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder)
+ elif not chown_ret:
+ logger.error("Could not chown plugin")
+ return
else:
logger.error("Could not download remote binaries")
return
diff --git a/backend/decky_loader/enums.py b/backend/decky_loader/enums.py
index e7fb4905..4a6296ad 100644
--- a/backend/decky_loader/enums.py
+++ b/backend/decky_loader/enums.py
@@ -1,9 +1,8 @@
from enum import IntEnum
class UserType(IntEnum):
- HOST_USER = 1
- EFFECTIVE_USER = 2
- ROOT = 3
+ HOST_USER = 1 # usually deck
+ EFFECTIVE_USER = 2 # usually root
class PluginLoadType(IntEnum):
LEGACY_EVAL_IIFE = 0 # legacy, uses legacy serverAPI
diff --git a/backend/decky_loader/helpers.py b/backend/decky_loader/helpers.py
index 857503ab..1621652f 100644
--- a/backend/decky_loader/helpers.py
+++ b/backend/decky_loader/helpers.py
@@ -181,7 +181,8 @@ def get_user_group_id() -> int:
# Get the default home path unless a user is specified
def get_home_path(username: str | None = None) -> str:
- return localplatform.get_home_path(UserType.ROOT if username == "root" else UserType.HOST_USER)
+ # TODO hardcoded root is kinda a hack
+ return localplatform.get_home_path(UserType.EFFECTIVE_USER if username == "root" else UserType.HOST_USER)
async def is_systemd_unit_active(unit_name: str) -> bool:
return await localplatform.service_active(unit_name)
diff --git a/backend/decky_loader/localplatform/localplatformlinux.py b/backend/decky_loader/localplatform/localplatformlinux.py
index beaa2540..1c8f2ace 100644
--- a/backend/decky_loader/localplatform/localplatformlinux.py
+++ b/backend/decky_loader/localplatform/localplatformlinux.py
@@ -59,8 +59,6 @@ def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool =
user_str = _get_user()+":"+_get_user_group()
elif user == UserType.EFFECTIVE_USER:
user_str = _get_effective_user()+":"+_get_effective_user_group()
- elif user == UserType.ROOT:
- user_str = "root:root"
else:
raise Exception("Unknown User Type")
@@ -87,7 +85,7 @@ def chmod(path : str, permissions : int, recursive : bool = True) -> bool:
return True
-def folder_owner(path : str) -> UserType|None:
+def file_owner(path : str) -> UserType|None:
user_owner = _get_user_owner(path)
if (user_owner == _get_user()):
@@ -106,13 +104,14 @@ def get_home_path(user : UserType = UserType.HOST_USER) -> str:
user_name = _get_user()
elif user == UserType.EFFECTIVE_USER:
user_name = _get_effective_user()
- elif user == UserType.ROOT:
- pass
else:
raise Exception("Unknown User Type")
return pwd.getpwnam(user_name).pw_dir
+def get_effective_username() -> str:
+ return _get_effective_user()
+
def get_username() -> str:
return _get_user()
@@ -121,8 +120,8 @@ def setgid(user : UserType = UserType.HOST_USER):
if user == UserType.HOST_USER:
user_id = _get_user_group_id()
- elif user == UserType.ROOT:
- pass
+ elif user == UserType.EFFECTIVE_USER:
+ pass # we already are
else:
raise Exception("Unknown user type")
@@ -133,8 +132,8 @@ def setuid(user : UserType = UserType.HOST_USER):
if user == UserType.HOST_USER:
user_id = _get_user_id()
- elif user == UserType.ROOT:
- pass
+ elif user == UserType.EFFECTIVE_USER:
+ pass # we already are
else:
raise Exception("Unknown user type")
diff --git a/backend/decky_loader/localplatform/localplatformwin.py b/backend/decky_loader/localplatform/localplatformwin.py
index 7e3bd31c..8755c943 100644
--- a/backend/decky_loader/localplatform/localplatformwin.py
+++ b/backend/decky_loader/localplatform/localplatformwin.py
@@ -7,7 +7,7 @@ def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool =
def chmod(path : str, permissions : int, recursive : bool = True) -> bool:
return True # Stubbed
-def folder_owner(path : str) -> UserType|None:
+def file_owner(path : str) -> UserType|None:
return UserType.HOST_USER # Stubbed
def get_home_path(user : UserType = UserType.HOST_USER) -> str:
@@ -34,6 +34,9 @@ async def service_restart(service_name : str, block : bool = True) -> bool:
return True # Stubbed
+def get_effective_username() -> str:
+ return os.getlogin()
+
def get_username() -> str:
return os.getlogin()
diff --git a/backend/decky_loader/main.py b/backend/decky_loader/main.py
index 315b7d29..16caca2e 100644
--- a/backend/decky_loader/main.py
+++ b/backend/decky_loader/main.py
@@ -50,7 +50,7 @@ def chown_plugin_dir():
if not path.exists(plugin_path): # For safety, create the folder before attempting to do anything with it
mkdir_as_user(plugin_path)
- if not chown(plugin_path, UserType.HOST_USER) or not chmod(plugin_path, 555):
+ if not chown(plugin_path, UserType.EFFECTIVE_USER, False) or not chmod(plugin_path, 755, False):
logger.error(f"chown/chmod exited with a non-zero exit code")
if get_chown_plugin_path() == True:
diff --git a/backend/decky_loader/plugin/plugin.py b/backend/decky_loader/plugin/plugin.py
index ba23fff9..61de4b1f 100644
--- a/backend/decky_loader/plugin/plugin.py
+++ b/backend/decky_loader/plugin/plugin.py
@@ -8,7 +8,8 @@ from traceback import format_exc
from .sandboxed_plugin import SandboxedPlugin
from .messages import MethodCallRequest, SocketMessageType
-from ..enums import PluginLoadType
+from ..enums import PluginLoadType, UserType
+from ..localplatform.localplatform import file_owner, chown, chmod, get_chown_plugin_path
from ..localplatform.localsocket import LocalSocket
from ..helpers import get_homebrew_path, mkdir_as_user
@@ -26,9 +27,12 @@ class PluginWrapper:
self.load_type = PluginLoadType.LEGACY_EVAL_IIFE.value
- json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r", encoding="utf-8"))
- if path.isfile(path.join(plugin_path, plugin_directory, "package.json")):
- package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r", encoding="utf-8"))
+ plugin_dir_path = path.join(plugin_path, plugin_directory)
+ plugin_json_path = path.join(plugin_dir_path, "plugin.json")
+
+ json = load(open(plugin_json_path, "r", encoding="utf-8"))
+ if path.isfile(path.join(plugin_dir_path, "package.json")):
+ package_json = load(open(path.join(plugin_dir_path, "package.json"), "r", encoding="utf-8"))
self.version = package_json["version"]
if ("type" in package_json and package_json["type"] == "module"):
self.load_type = PluginLoadType.ESMODULE_V1.value
@@ -42,6 +46,17 @@ class PluginWrapper:
self.log = getLogger("plugin")
+ if get_chown_plugin_path():
+ # ensure plugin folder ownership
+ if file_owner(plugin_dir_path) != UserType.EFFECTIVE_USER:
+ chown(plugin_dir_path, UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER, True)
+ chown(plugin_dir_path, UserType.EFFECTIVE_USER, False)
+ chmod(plugin_dir_path, 755, True)
+ # fix plugin.json permissions
+ if file_owner(plugin_json_path) != UserType.EFFECTIVE_USER:
+ chown(plugin_json_path, UserType.EFFECTIVE_USER, False)
+ chmod(plugin_json_path, 755, False)
+
self.sandboxed_plugin = SandboxedPlugin(self.name, self.passive, self.flags, self.file, self.plugin_directory, self.plugin_path, self.version, self.author, self.api_version)
self.proc: Process | None = None
self._socket = LocalSocket()
diff --git a/backend/decky_loader/plugin/sandboxed_plugin.py b/backend/decky_loader/plugin/sandboxed_plugin.py
index 7d88719f..71e1d17b 100644
--- a/backend/decky_loader/plugin/sandboxed_plugin.py
+++ b/backend/decky_loader/plugin/sandboxed_plugin.py
@@ -13,7 +13,7 @@ from .messages import SocketResponseDict, SocketMessageType
from ..localplatform.localsocket import LocalSocket
from ..localplatform.localplatform import setgid, setuid, get_username, get_home_path, ON_LINUX
from ..enums import UserType
-from .. import helpers, settings, injector # pyright: ignore [reportUnusedImport]
+from .. import helpers
from typing import List, TypeVar, Any
@@ -61,10 +61,10 @@ class SandboxedPlugin:
if self.passive:
return
- setgid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
- setuid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
+ setgid(UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER)
+ setuid(UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER)
# export a bunch of environment variables to help plugin developers
- environ["HOME"] = get_home_path(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
+ environ["HOME"] = get_home_path(UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER)
environ["USER"] = "root" if "root" in self.flags else get_username()
environ["DECKY_VERSION"] = helpers.get_loader_version()
environ["DECKY_USER"] = get_username()
diff --git a/backend/decky_loader/settings.py b/backend/decky_loader/settings.py
index b5f034aa..73dbe38a 100644
--- a/backend/decky_loader/settings.py
+++ b/backend/decky_loader/settings.py
@@ -1,7 +1,7 @@
from json import dump, load
from os import mkdir, path, listdir, rename
from typing import Any, Dict
-from .localplatform.localplatform import chown, folder_owner, get_chown_plugin_path
+from .localplatform.localplatform import chown, file_owner, get_chown_plugin_path
from .enums import UserType
from .helpers import get_homebrew_path
@@ -28,8 +28,8 @@ class SettingsManager:
#If the owner of the settings directory is not the user, then set it as the user:
- expected_user = UserType.HOST_USER if get_chown_plugin_path() else UserType.ROOT
- if folder_owner(settings_directory) != expected_user:
+ expected_user = UserType.HOST_USER if get_chown_plugin_path() else UserType.EFFECTIVE_USER
+ if file_owner(settings_directory) != expected_user:
chown(settings_directory, expected_user, False)
self.settings: Dict[str, Any] = {}
diff --git a/backend/decky_loader/wsrouter.py b/backend/decky_loader/wsrouter.py
index 8a019b44..691abd05 100644
--- a/backend/decky_loader/wsrouter.py
+++ b/backend/decky_loader/wsrouter.py
@@ -7,7 +7,7 @@ from aiohttp.web import Application, WebSocketResponse, Request, Response, get
from enum import IntEnum
-from typing import Callable, Coroutine, Dict, Any, cast, TypeVar
+from typing import Callable, Coroutine, Dict, Any, cast
from traceback import format_exc
@@ -29,8 +29,6 @@ class WSMessageExtra(WSMessage):
# see wsrouter.ts for typings
-DataType = TypeVar("DataType")
-
Route = Callable[..., Coroutine[Any, Any, Any]]
class WSRouter: