mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-11 19:11:58 +00:00
Feat/configurable paths (#404)
This commit is contained in:
committed by
GitHub
parent
4777963b65
commit
d6f336d84b
+2
-2
@@ -36,9 +36,9 @@ async def csrf_middleware(request, handler):
|
|||||||
return await handler(request)
|
return await handler(request)
|
||||||
return Response(text='Forbidden', status='403')
|
return Response(text='Forbidden', status='403')
|
||||||
|
|
||||||
# Get the default homebrew path unless a home_path is specified
|
# Get the default homebrew path unless a home_path is specified. home_path argument is deprecated
|
||||||
def get_homebrew_path(home_path = None) -> str:
|
def get_homebrew_path(home_path = None) -> str:
|
||||||
return os.path.join(home_path if home_path != None else localplatform.get_home_path(), "homebrew")
|
return localplatform.get_unprivileged_path()
|
||||||
|
|
||||||
# Recursively create path and chown as user
|
# Recursively create path and chown as user
|
||||||
def mkdir_as_user(path):
|
def mkdir_as_user(path):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import platform
|
import platform, os
|
||||||
|
|
||||||
ON_WINDOWS = platform.system() == "Windows"
|
ON_WINDOWS = platform.system() == "Windows"
|
||||||
ON_LINUX = not ON_WINDOWS
|
ON_LINUX = not ON_WINDOWS
|
||||||
@@ -9,3 +9,32 @@ if ON_WINDOWS:
|
|||||||
else:
|
else:
|
||||||
from localplatformlinux import *
|
from localplatformlinux import *
|
||||||
import localplatformlinux as localplatform
|
import localplatformlinux as localplatform
|
||||||
|
|
||||||
|
def get_privileged_path() -> str:
|
||||||
|
'''Get path accessible by elevated user. Holds plugins, decky loader and decky loader configs'''
|
||||||
|
return localplatform.get_privileged_path()
|
||||||
|
|
||||||
|
def get_unprivileged_path() -> str:
|
||||||
|
'''Get path accessible by non-elevated user. Holds plugin configuration, plugin data and plugin logs. Externally referred to as the 'Homebrew' directory'''
|
||||||
|
return localplatform.get_unprivileged_path()
|
||||||
|
|
||||||
|
def get_unprivileged_user() -> str:
|
||||||
|
'''Get user that should own files made in unprivileged path'''
|
||||||
|
return localplatform.get_unprivileged_user()
|
||||||
|
|
||||||
|
def get_chown_plugin_path() -> bool:
|
||||||
|
return os.getenv("CHOWN_PLUGIN_PATH", "1") == "1"
|
||||||
|
|
||||||
|
def get_server_host() -> str:
|
||||||
|
return os.getenv("SERVER_HOST", "127.0.0.1")
|
||||||
|
|
||||||
|
def get_server_port() -> int:
|
||||||
|
return int(os.getenv("SERVER_PORT", "1337"))
|
||||||
|
|
||||||
|
def get_live_reload() -> bool:
|
||||||
|
os.getenv("LIVE_RELOAD", "1") == "1"
|
||||||
|
|
||||||
|
def get_log_level() -> int:
|
||||||
|
return {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[
|
||||||
|
os.getenv("LOG_LEVEL", "INFO")
|
||||||
|
]
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
import os, pwd, grp, sys
|
import os, pwd, grp, sys, logging
|
||||||
from subprocess import call, run, DEVNULL, PIPE, STDOUT
|
from subprocess import call, run, DEVNULL, PIPE, STDOUT
|
||||||
from customtypes import UserType
|
from customtypes import UserType
|
||||||
|
|
||||||
|
logger = logging.getLogger("localplatform")
|
||||||
|
|
||||||
# Get the user id hosting the plugin loader
|
# Get the user id hosting the plugin loader
|
||||||
def _get_user_id() -> int:
|
def _get_user_id() -> int:
|
||||||
proc_path = os.path.realpath(sys.argv[0])
|
return pwd.getpwnam(_get_user()).pw_uid
|
||||||
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
|
# Get the user hosting the plugin loader
|
||||||
def _get_user() -> str:
|
def _get_user() -> str:
|
||||||
return pwd.getpwuid(_get_user_id()).pw_name
|
return get_unprivileged_user()
|
||||||
|
|
||||||
# Get the effective user id of the running process
|
# Get the effective user id of the running process
|
||||||
def _get_effective_user_id() -> int:
|
def _get_effective_user_id() -> int:
|
||||||
@@ -50,10 +47,12 @@ def _get_user_group() -> str:
|
|||||||
def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool:
|
def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool:
|
||||||
user_str = ""
|
user_str = ""
|
||||||
|
|
||||||
if (user == UserType.HOST_USER):
|
if user == UserType.HOST_USER:
|
||||||
user_str = _get_user()+":"+_get_user_group()
|
user_str = _get_user()+":"+_get_user_group()
|
||||||
elif (user == UserType.EFFECTIVE_USER):
|
elif user == UserType.EFFECTIVE_USER:
|
||||||
user_str = _get_effective_user()+":"+_get_effective_user_group()
|
user_str = _get_effective_user()+":"+_get_effective_user_group()
|
||||||
|
elif user == UserType.ROOT:
|
||||||
|
user_str = "root:root"
|
||||||
else:
|
else:
|
||||||
raise Exception("Unknown User Type")
|
raise Exception("Unknown User Type")
|
||||||
|
|
||||||
@@ -136,3 +135,60 @@ async def service_start(service_name : str) -> bool:
|
|||||||
cmd = ["systemctl", "start", service_name]
|
cmd = ["systemctl", "start", service_name]
|
||||||
res = run(cmd, stdout=PIPE, stderr=STDOUT)
|
res = run(cmd, stdout=PIPE, stderr=STDOUT)
|
||||||
return res.returncode == 0
|
return res.returncode == 0
|
||||||
|
|
||||||
|
def get_privileged_path() -> str:
|
||||||
|
path = os.getenv("PRIVILEGED_PATH")
|
||||||
|
|
||||||
|
if path == None:
|
||||||
|
path = get_unprivileged_path()
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
def _parent_dir(path : str) -> str:
|
||||||
|
if path == None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if path.endswith('/'):
|
||||||
|
path = path[:-1]
|
||||||
|
|
||||||
|
return os.path.dirname(path)
|
||||||
|
|
||||||
|
def get_unprivileged_path() -> str:
|
||||||
|
path = os.getenv("UNPRIVILEGED_PATH")
|
||||||
|
|
||||||
|
if path == None:
|
||||||
|
path = _parent_dir(os.getenv("PLUGIN_PATH"))
|
||||||
|
|
||||||
|
if path == None:
|
||||||
|
logger.debug("Unprivileged path is not properly configured. Making something up!")
|
||||||
|
# Expected path of loader binary is /home/deck/homebrew/service/PluginLoader
|
||||||
|
path = _parent_dir(_parent_dir(os.path.realpath(sys.argv[0])))
|
||||||
|
|
||||||
|
if not os.path.exists(path):
|
||||||
|
path = None
|
||||||
|
|
||||||
|
if path == None:
|
||||||
|
logger.warn("Unprivileged path is not properly configured. Defaulting to /home/deck/homebrew")
|
||||||
|
path = "/home/deck/homebrew" # We give up
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def get_unprivileged_user() -> str:
|
||||||
|
user = os.getenv("UNPRIVILEGED_USER")
|
||||||
|
|
||||||
|
if user == None:
|
||||||
|
# Lets hope we can extract it from the unprivileged dir
|
||||||
|
dir = os.path.realpath(get_unprivileged_path())
|
||||||
|
|
||||||
|
pws = sorted(pwd.getpwall(), reverse=True, key=lambda pw: len(pw.pw_dir))
|
||||||
|
for pw in pws:
|
||||||
|
if dir.startswith(os.path.realpath(pw.pw_dir)):
|
||||||
|
user = pw.pw_name
|
||||||
|
break
|
||||||
|
|
||||||
|
if user == None:
|
||||||
|
logger.warn("Unprivileged user is not properly configured. Defaulting to 'deck'")
|
||||||
|
user = 'deck'
|
||||||
|
|
||||||
|
return user
|
||||||
@@ -36,3 +36,18 @@ async def service_restart(service_name : str) -> bool:
|
|||||||
|
|
||||||
def get_username() -> str:
|
def get_username() -> str:
|
||||||
return os.getlogin()
|
return os.getlogin()
|
||||||
|
|
||||||
|
def get_privileged_path() -> str:
|
||||||
|
'''On windows, privileged_path is equal to unprivileged_path'''
|
||||||
|
return get_unprivileged_path()
|
||||||
|
|
||||||
|
def get_unprivileged_path() -> str:
|
||||||
|
path = os.getenv("UNPRIVILEGED_PATH")
|
||||||
|
|
||||||
|
if path == None:
|
||||||
|
path = os.getenv("PRIVILEGED_PATH", os.path.join(os.path.expanduser("~"), "homebrew"))
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
def get_unprivileged_user() -> str:
|
||||||
|
return os.getenv("UNPRIVILEGED_USER", os.getlogin())
|
||||||
|
|||||||
+16
-22
@@ -1,6 +1,10 @@
|
|||||||
# Change PyInstaller files permissions
|
# Change PyInstaller files permissions
|
||||||
import sys
|
import sys
|
||||||
from localplatform import chmod, chown, service_stop, service_start, ON_WINDOWS
|
from localplatform import (chmod, chown, service_stop, service_start,
|
||||||
|
ON_WINDOWS, get_log_level, get_live_reload,
|
||||||
|
get_server_port, get_server_host, get_chown_plugin_path,
|
||||||
|
get_unprivileged_user, get_unprivileged_path,
|
||||||
|
get_privileged_path)
|
||||||
if hasattr(sys, '_MEIPASS'):
|
if hasattr(sys, '_MEIPASS'):
|
||||||
chmod(sys._MEIPASS, 755)
|
chmod(sys._MEIPASS, 755)
|
||||||
# Full imports
|
# Full imports
|
||||||
@@ -20,7 +24,7 @@ from aiohttp_jinja2 import setup as jinja_setup
|
|||||||
# local modules
|
# local modules
|
||||||
from browser import PluginBrowser
|
from browser import PluginBrowser
|
||||||
from helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token,
|
from helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token,
|
||||||
get_homebrew_path, mkdir_as_user, get_system_pythonpaths)
|
mkdir_as_user, get_system_pythonpaths)
|
||||||
|
|
||||||
from injector import get_gamepadui_tab, Tab, get_tabs, close_old_tabs
|
from injector import get_gamepadui_tab, Tab, get_tabs, close_old_tabs
|
||||||
from loader import Loader
|
from loader import Loader
|
||||||
@@ -29,33 +33,23 @@ from updater import Updater
|
|||||||
from utilities import Utilities
|
from utilities import Utilities
|
||||||
from customtypes import UserType
|
from customtypes import UserType
|
||||||
|
|
||||||
HOMEBREW_PATH = get_homebrew_path()
|
|
||||||
CONFIG = {
|
|
||||||
"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")),
|
|
||||||
"live_reload": getenv("LIVE_RELOAD", "1") == "1",
|
|
||||||
"log_level": {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[
|
|
||||||
getenv("LOG_LEVEL", "INFO")
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
basicConfig(
|
basicConfig(
|
||||||
level=CONFIG["log_level"],
|
level=get_log_level(),
|
||||||
format="[%(module)s][%(levelname)s]: %(message)s"
|
format="[%(module)s][%(levelname)s]: %(message)s"
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = getLogger("Main")
|
logger = getLogger("Main")
|
||||||
|
plugin_path = path.join(get_privileged_path(), "plugins")
|
||||||
|
|
||||||
def chown_plugin_dir():
|
def chown_plugin_dir():
|
||||||
if not path.exists(CONFIG["plugin_path"]): # For safety, create the folder before attempting to do anything with it
|
if not path.exists(plugin_path): # For safety, create the folder before attempting to do anything with it
|
||||||
mkdir_as_user(CONFIG["plugin_path"])
|
mkdir_as_user(plugin_path)
|
||||||
|
|
||||||
if not chown(CONFIG["plugin_path"], UserType.HOST_USER) or not chmod(CONFIG["plugin_path"], 555):
|
if not chown(plugin_path, UserType.HOST_USER) or not chmod(plugin_path, 555):
|
||||||
logger.error(f"chown/chmod exited with a non-zero exit code")
|
logger.error(f"chown/chmod exited with a non-zero exit code")
|
||||||
|
|
||||||
if CONFIG["chown_plugin_path"] == True:
|
if get_chown_plugin_path() == True:
|
||||||
chown_plugin_dir()
|
chown_plugin_dir()
|
||||||
|
|
||||||
class PluginManager:
|
class PluginManager:
|
||||||
@@ -70,9 +64,9 @@ class PluginManager:
|
|||||||
allow_credentials=True
|
allow_credentials=True
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"], self.loop, CONFIG["live_reload"])
|
self.plugin_loader = Loader(self.web_app, plugin_path, self.loop, get_live_reload())
|
||||||
self.settings = SettingsManager("loader", path.join(HOMEBREW_PATH, "settings"))
|
self.settings = SettingsManager("loader", path.join(get_privileged_path(), "settings"))
|
||||||
self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.plugin_loader.plugins, self.plugin_loader, self.settings)
|
self.plugin_browser = PluginBrowser(plugin_path, self.plugin_loader.plugins, self.plugin_loader, self.settings)
|
||||||
self.utilities = Utilities(self)
|
self.utilities = Utilities(self)
|
||||||
self.updater = Updater(self)
|
self.updater = Updater(self)
|
||||||
|
|
||||||
@@ -174,7 +168,7 @@ class PluginManager:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
return run_app(self.web_app, host=CONFIG["server_host"], port=CONFIG["server_port"], loop=self.loop, access_log=None)
|
return run_app(self.web_app, host=get_server_host(), port=get_server_port(), loop=self.loop, access_log=None)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if ON_WINDOWS:
|
if ON_WINDOWS:
|
||||||
|
|||||||
+4
-5
@@ -1,6 +1,6 @@
|
|||||||
from json import dump, load
|
from json import dump, load
|
||||||
from os import mkdir, path, listdir, rename
|
from os import mkdir, path, listdir, rename
|
||||||
from localplatform import chown, folder_owner
|
from localplatform import chown, folder_owner, get_chown_plugin_path
|
||||||
from customtypes import UserType
|
from customtypes import UserType
|
||||||
|
|
||||||
from helpers import get_homebrew_path
|
from helpers import get_homebrew_path
|
||||||
@@ -17,7 +17,6 @@ class SettingsManager:
|
|||||||
#Create the folder with the correct permission
|
#Create the folder with the correct permission
|
||||||
if not path.exists(settings_directory):
|
if not path.exists(settings_directory):
|
||||||
mkdir(settings_directory)
|
mkdir(settings_directory)
|
||||||
chown(settings_directory)
|
|
||||||
|
|
||||||
#Copy all old settings file in the root directory to the correct folder
|
#Copy all old settings file in the root directory to the correct folder
|
||||||
for file in listdir(wrong_dir):
|
for file in listdir(wrong_dir):
|
||||||
@@ -28,9 +27,9 @@ class SettingsManager:
|
|||||||
|
|
||||||
|
|
||||||
#If the owner of the settings directory is not the user, then set it as the user:
|
#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) != UserType.HOST_USER:
|
if folder_owner(settings_directory) != expected_user:
|
||||||
chown(settings_directory, UserType.HOST_USER, False)
|
chown(settings_directory, expected_user, False)
|
||||||
|
|
||||||
self.settings = {}
|
self.settings = {}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -9,7 +9,8 @@ Restart=always
|
|||||||
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
||||||
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
||||||
KillSignal=SIGKILL
|
KillSignal=SIGKILL
|
||||||
Environment=PLUGIN_PATH=${HOMEBREW_FOLDER}/plugins
|
Environment=UNPRIVILEGED_PATH=${HOMEBREW_FOLDER}
|
||||||
|
Environment=PRIVILEGED_PATH=${HOMEBREW_FOLDER}
|
||||||
Environment=LOG_LEVEL=DEBUG
|
Environment=LOG_LEVEL=DEBUG
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
Vendored
+2
-1
@@ -9,7 +9,8 @@ Restart=always
|
|||||||
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
|
||||||
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
WorkingDirectory=${HOMEBREW_FOLDER}/services
|
||||||
KillSignal=SIGKILL
|
KillSignal=SIGKILL
|
||||||
Environment=PLUGIN_PATH=${HOMEBREW_FOLDER}/plugins
|
Environment=UNPRIVILEGED_PATH=${HOMEBREW_FOLDER}
|
||||||
|
Environment=PRIVILEGED_PATH=${HOMEBREW_FOLDER}
|
||||||
Environment=LOG_LEVEL=INFO
|
Environment=LOG_LEVEL=INFO
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
Reference in New Issue
Block a user