summaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
authorPhilipp Richter <richterphilipp.pops@gmail.com>2023-01-23 00:54:05 +0000
committerGitHub <noreply@github.com>2023-01-22 16:54:05 -0800
commitc2b76d9099ac551f829f9cec821c23a9aff6b018 (patch)
tree30102595e2f19779cc81a6e146e157d5a17e1211 /backend
parentc05e8f9ae0a38a58e73e98589133e9140eb8f46a (diff)
downloaddecky-loader-c2b76d9099ac551f829f9cec821c23a9aff6b018.tar.gz
decky-loader-c2b76d9099ac551f829f9cec821c23a9aff6b018.zip
Expose useful env vars to plugin processes (#349)v2.5.2-pre1v2.5.2
* recommended paths for storing data * improve helper functions
Diffstat (limited to 'backend')
-rw-r--r--backend/helpers.py111
-rw-r--r--backend/main.py14
-rw-r--r--backend/plugin.py25
-rw-r--r--backend/settings.py10
-rw-r--r--backend/updater.py6
5 files changed, 98 insertions, 68 deletions
diff --git a/backend/helpers.py b/backend/helpers.py
index b97352bd..7cab512b 100644
--- a/backend/helpers.py
+++ b/backend/helpers.py
@@ -5,6 +5,7 @@ import ssl
import subprocess
import uuid
import os
+import sys
from subprocess import check_output
from time import sleep
from hashlib import sha256
@@ -19,8 +20,6 @@ REMOTE_DEBUGGER_UNIT = "steam-web-debug-portforward.service"
# global vars
csrf_token = str(uuid.uuid4())
ssl_ctx = ssl.create_default_context(cafile=certifi.where())
-user = None
-group = None
assets_regex = re.compile("^/plugins/.*/assets/.*")
frontend_regex = re.compile("^/frontend/.*")
@@ -37,65 +36,87 @@ async def csrf_middleware(request, handler):
return await handler(request)
return Response(text='Forbidden', status='403')
-# Get the user by checking for the first logged in user. As this is run
-# by systemd at startup the process is likely to start before the user
-# logs in, so we will wait here until they are available. Note that
-# other methods such as getenv wont work as there was no $SUDO_USER to
-# start the systemd service.
+# Deprecated
def set_user():
- global user
- cmd = "who | awk '{print $1}' | sort | head -1"
- while user == None:
- name = check_output(cmd, shell=True).decode().strip()
- if name not in [None, '']:
- user = name
- sleep(0.1)
-
-# Get the global user. get_user must be called first.
+ pass
+
+# Get the user id hosting the plugin loader
+def get_user_id() -> int:
+ proc_path = os.path.realpath(sys.argv[0])
+ pws = sorted(pwd.getpwall(), reverse=True, key=lambda pw: len(pw.pw_dir))
+ for pw in pws:
+ if proc_path.startswith(os.path.realpath(pw.pw_dir)):
+ return pw.pw_uid
+ raise PermissionError("The plugin loader does not seem to be hosted by any known user.")
+
+# Get the user hosting the plugin loader
def get_user() -> str:
- global user
- if user == None:
- raise ValueError("helpers.get_user method called before user variable was set. Run helpers.set_user first.")
- return user
+ return pwd.getpwuid(get_user_id()).pw_name
-#Get the user owner of the given file path.
+# Get the effective user id of the running process
+def get_effective_user_id() -> int:
+ return os.geteuid()
+
+# Get the effective user of the running process
+def get_effective_user() -> str:
+ return pwd.getpwuid(get_effective_user_id()).pw_name
+
+# Get the effective user group id of the running process
+def get_effective_user_group_id() -> int:
+ return os.getegid()
+
+# Get the effective user group of the running process
+def get_effective_user_group() -> str:
+ return grp.getgrgid(get_effective_user_group_id()).gr_name
+
+# Get the user owner of the given file path.
def get_user_owner(file_path) -> str:
- return pwd.getpwuid(os.stat(file_path).st_uid)[0]
+ return pwd.getpwuid(os.stat(file_path).st_uid).pw_name
-#Get the user group of the given file path.
+# Get the user group of the given file path.
def get_user_group(file_path) -> str:
- return grp.getgrgid(os.stat(file_path).st_gid)[0]
+ return grp.getgrgid(os.stat(file_path).st_gid).gr_name
-# Set the global user group. get_user must be called first
+# Deprecated
def set_user_group() -> str:
- global group
- global user
- if user == None:
- raise ValueError("helpers.set_user_dir method called before user variable was set. Run helpers.set_user first.")
- if group == None:
- group = check_output(["id", "-g", "-n", user]).decode().strip()
-
-# Get the group of the global user. set_user_group must be called first.
+ return get_user_group()
+
+# Get the group id of the user hosting the plugin loader
+def get_user_group_id() -> int:
+ return pwd.getpwuid(get_user_id()).pw_gid
+
+# Get the group of the user hosting the plugin loader
def get_user_group() -> str:
- global group
- if group == None:
- raise ValueError("helpers.get_user_group method called before group variable was set. Run helpers.set_user_group first.")
- return group
+ return grp.getgrgid(get_user_group_id()).gr_name
# Get the default home path unless a user is specified
def get_home_path(username = None) -> str:
if username == None:
- raise ValueError("Username not defined, no home path can be found.")
- else:
- return str("/home/"+username)
+ username = get_user()
+ return pwd.getpwnam(username).pw_dir
-# Get the default homebrew path unless a user is specified
+# Get the default homebrew path unless a home_path is specified
def get_homebrew_path(home_path = None) -> str:
if home_path == None:
- raise ValueError("Home path not defined, homebrew dir cannot be determined.")
- else:
- return str(home_path+"/homebrew")
- # return str(home_path+"/homebrew")
+ home_path = get_home_path()
+ return os.path.join(home_path, "homebrew")
+
+# Recursively create path and chown as user
+def mkdir_as_user(path):
+ path = os.path.realpath(path)
+ os.makedirs(path, exist_ok=True)
+ chown_path = get_home_path()
+ parts = os.path.relpath(path, chown_path).split(os.sep)
+ uid = get_user_id()
+ gid = get_user_group_id()
+ for p in parts:
+ chown_path = os.path.join(chown_path, p)
+ os.chown(chown_path, uid, gid)
+
+# Fetches the version of loader
+def get_loader_version() -> str:
+ with open(os.path.join(os.path.dirname(sys.argv[0]), ".loader.version"), "r", encoding="utf-8") as version_file:
+ return version_file.readline().replace("\n", "")
# Download Remote Binaries to local Plugin
async def download_remote_binary_to_path(url, binHash, path) -> bool:
diff --git a/backend/main.py b/backend/main.py
index c48ad752..a2ac008a 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -19,8 +19,7 @@ from aiohttp_jinja2 import setup as jinja_setup
# local modules
from browser import PluginBrowser
from helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token,
- get_home_path, get_homebrew_path, get_user,
- get_user_group, set_user, set_user_group,
+ get_home_path, get_homebrew_path, get_user, get_user_group,
stop_systemd_unit, start_systemd_unit)
from injector import get_gamepadui_tab, Tab, get_tabs, close_old_tabs
from loader import Loader
@@ -28,18 +27,11 @@ from settings import SettingsManager
from updater import Updater
from utilities import Utilities
-# Ensure USER and GROUP vars are set first.
-# TODO: This isn't the best way to do this but supports the current
-# implementation. All the config load and environment setting eventually be
-# moved into init or a config/loader method.
-set_user()
-set_user_group()
USER = get_user()
GROUP = get_user_group()
-HOME_PATH = "/home/"+USER
-HOMEBREW_PATH = HOME_PATH+"/homebrew"
+HOMEBREW_PATH = get_homebrew_path()
CONFIG = {
- "plugin_path": getenv("PLUGIN_PATH", HOMEBREW_PATH+"/plugins"),
+ "plugin_path": getenv("PLUGIN_PATH", path.join(HOMEBREW_PATH, "plugins")),
"chown_plugin_path": getenv("CHOWN_PLUGIN_PATH", "1") == "1",
"server_host": getenv("SERVER_HOST", "127.0.0.1"),
"server_port": int(getenv("SERVER_PORT", "1337")),
diff --git a/backend/plugin.py b/backend/plugin.py
index e21d5bde..df0efe16 100644
--- a/backend/plugin.py
+++ b/backend/plugin.py
@@ -7,10 +7,12 @@ from importlib.util import module_from_spec, spec_from_file_location
from json import dumps, load, loads
from logging import getLogger
from traceback import format_exc
-from os import path, setgid, setuid
+from os import path, setgid, setuid, environ
from signal import SIGINT, signal
from sys import exit
from time import time
+import helpers
+from updater import Updater
multiprocessing.set_start_method("fork")
@@ -19,6 +21,7 @@ BUFFER_LIMIT = 2 ** 20 # 1 MiB
class PluginWrapper:
def __init__(self, file, plugin_directory, plugin_path) -> None:
self.file = file
+ self.plugin_path = plugin_path
self.plugin_directory = plugin_directory
self.reader = None
self.writer = None
@@ -56,8 +59,24 @@ class PluginWrapper:
set_event_loop(new_event_loop())
if self.passive:
return
- setgid(0 if "root" in self.flags else 1000)
- setuid(0 if "root" in self.flags else 1000)
+ setgid(0 if "root" in self.flags else helpers.get_user_group_id())
+ setuid(0 if "root" in self.flags else helpers.get_user_id())
+ # export a bunch of environment variables to help plugin developers
+ environ["HOME"] = helpers.get_home_path("root" if "root" in self.flags else helpers.get_user())
+ environ["USER"] = "root" if "root" in self.flags else helpers.get_user()
+ environ["DECKY_VERSION"] = helpers.get_loader_version()
+ environ["DECKY_USER"] = helpers.get_user()
+ environ["DECKY_HOME"] = helpers.get_homebrew_path()
+ environ["DECKY_PLUGIN_SETTINGS_DIR"] = path.join(environ["DECKY_HOME"], "settings", self.plugin_directory)
+ helpers.mkdir_as_user(environ["DECKY_PLUGIN_SETTINGS_DIR"])
+ environ["DECKY_PLUGIN_RUNTIME_DIR"] = path.join(environ["DECKY_HOME"], "data", self.plugin_directory)
+ helpers.mkdir_as_user(environ["DECKY_PLUGIN_RUNTIME_DIR"])
+ environ["DECKY_PLUGIN_LOG_DIR"] = path.join(environ["DECKY_HOME"], "logs", self.plugin_directory)
+ helpers.mkdir_as_user(environ["DECKY_PLUGIN_LOG_DIR"])
+ environ["DECKY_PLUGIN_DIR"] = path.join(self.plugin_path, self.plugin_directory)
+ environ["DECKY_PLUGIN_NAME"] = self.name
+ environ["DECKY_PLUGIN_VERSION"] = self.version
+ environ["DECKY_PLUGIN_AUTHOR"] = self.author
spec = spec_from_file_location("_", self.file)
module = module_from_spec(spec)
spec.loader.exec_module(module)
diff --git a/backend/settings.py b/backend/settings.py
index 6dedcbbe..64b04c60 100644
--- a/backend/settings.py
+++ b/backend/settings.py
@@ -2,14 +2,14 @@ from json import dump, load
from os import mkdir, path, listdir, rename
from shutil import chown
-from helpers import get_home_path, get_homebrew_path, get_user, set_user, get_user_owner
+from helpers import get_home_path, get_homebrew_path, get_user, get_user_group, get_user_owner
class SettingsManager:
def __init__(self, name, settings_directory = None) -> None:
- set_user()
USER = get_user()
- wrong_dir = get_homebrew_path(get_home_path(USER))
+ GROUP = get_user_group()
+ wrong_dir = get_homebrew_path()
if settings_directory == None:
settings_directory = path.join(wrong_dir, "settings")
@@ -18,7 +18,7 @@ class SettingsManager:
#Create the folder with the correct permission
if not path.exists(settings_directory):
mkdir(settings_directory)
- chown(settings_directory, USER, USER)
+ chown(settings_directory, USER, GROUP)
#Copy all old settings file in the root directory to the correct folder
for file in listdir(wrong_dir):
@@ -30,7 +30,7 @@ class SettingsManager:
#If the owner of the settings directory is not the user, then set it as the user:
if get_user_owner(settings_directory) != USER:
- chown(settings_directory, USER, USER)
+ chown(settings_directory, USER, GROUP)
self.settings = {}
diff --git a/backend/updater.py b/backend/updater.py
index 15a93e8a..14fd2070 100644
--- a/backend/updater.py
+++ b/backend/updater.py
@@ -31,9 +31,7 @@ class Updater:
self.remoteVer = None
self.allRemoteVers = None
try:
- logger.info(getcwd())
- with open(path.join(getcwd(), ".loader.version"), "r", encoding="utf-8") as version_file:
- self.localVer = version_file.readline().replace("\n", "")
+ self.localVer = helpers.get_loader_version()
except:
self.localVer = False
@@ -161,7 +159,7 @@ class Updater:
logger.error(f"Error at %s", exc_info=e)
with open(path.join(getcwd(), "plugin_loader.service"), "r", encoding="utf-8") as service_file:
service_data = service_file.read()
- service_data = service_data.replace("${HOMEBREW_FOLDER}", "/home/"+helpers.get_user()+"/homebrew")
+ service_data = service_data.replace("${HOMEBREW_FOLDER}", helpers.get_homebrew_path())
with open(path.join(getcwd(), "plugin_loader.service"), "w", encoding="utf-8") as service_file:
service_file.write(service_data)