diff --git a/src/backend/config/config.toml b/src/backend/config/config.toml index 9b9b865..adfb8cb 100644 --- a/src/backend/config/config.toml +++ b/src/backend/config/config.toml @@ -1,8 +1,12 @@ [frontend] build = "npm run build" +install = "npm install" show_output = true motd = "Memory: {{teal}}{{memory_used}} MB{{end}} / {{memory_total}} MB\nCPU: {{teal}}{{cpu_percent}}%{{end}}\nUsers: {{teal}}{{active_users}}{{end}}\nHave a great day!" +[backend] +install_plugin_deps = "pip install -r" + [login] disable_statuses = [] widgets = ["header", "version", "motd", "status", "sysinfo"] diff --git a/src/backend/plugins/dependencies.py b/src/backend/plugins/dependencies.py new file mode 100644 index 0000000..63ffe43 --- /dev/null +++ b/src/backend/plugins/dependencies.py @@ -0,0 +1,66 @@ +import json +import logging +import os +import subprocess +import sys + +from plugins import unpack +from utils import config +from utils.const import FRONTEND_PATH, PLUGINS_DIR + + +def python(plugin_name: str) -> bool: + req_path = f'{PLUGINS_DIR}/{plugin_name}/requirements.txt' + if not os.path.exists(req_path): + logging.warning(f'"requirements.txt" for plugin "{ + plugin_name}" does not exist') + return True + + # TODO: Check the returned value + try: + subprocess.check_call( + [sys.executable, '-m', *config.fetch().get('backend').get('install_plugin_deps').split(' '), req_path]) + except Exception as e: + unpack.revert(plugin_name) + raise e + + return True + + +def node(plugin_name: str) -> bool: + plugin_dir = f'{PLUGINS_DIR}/{plugin_name}' + if not os.path.exists(f'{plugin_dir}/package.json'): + logging.warning(f'"package.json" for plugin "{ + plugin_name}" does not exist') + return True + + if not _merge_dependencies(plugin_dir): + return False + + # TODO: Check the returned value + subprocess.check_call(args=config.fetch().get('frontend').get( + 'install').split(' '), cwd=FRONTEND_PATH, shell=True) + return True + + +def _merge_dependencies(plugin_dir: str) -> bool: + with open(f'{plugin_dir}/package.json', 'r', encoding='utf-8') as f: + source_data = json.load(f) + plugin_dependencies = source_data.get('dependencies') + f.close() + + with open(f'{FRONTEND_PATH}/package.json', 'r', encoding='utf-8') as f: + package_json = json.load(f) + f.close() + if package_json.get('dependencies') is None: + logging.error('Frontend\'s package.json is corrupted') + return False + + package_json['dependencies'].update(plugin_dependencies) + + with open(f'{FRONTEND_PATH}/package.json', 'w', encoding='utf-8') as f: + json.dump(package_json, f, indent=2) + f.write('\n') + f.close() + + return True diff --git a/src/backend/plugins/downloader.py b/src/backend/plugins/downloader.py index 76bffb9..3532216 100644 --- a/src/backend/plugins/downloader.py +++ b/src/backend/plugins/downloader.py @@ -1,6 +1,6 @@ from typing import Optional -from plugins import handler, priority, unpack, validate +from plugins import dependencies, handler, priority, unpack, validate from utils.const import PLUGINS_DOWNLOAD import logging @@ -21,14 +21,27 @@ def from_url(url: str) -> bool: name = config['plugin'].get('name').replace('-', '_') zip_url = config['plugin'].get('zip_url') - if not _download_plugin_zip(zip_url, name): - return False - if not unpack.unzip(name): - return False - if not unpack.unpack(name, 'Plugin.toml'): - return False - if not unpack.distribute(name): - return False + try: + if not _download_plugin_zip(zip_url, name): + return False + if not unpack.unzip(name): + unpack.revert(name) + return False + if not unpack.unpack(name, 'Plugin.toml'): + unpack.revert(name) + return False + if not unpack.distribute(name): + unpack.revert(name) + return False + if not dependencies.python(name): + unpack.revert(name) + return False + if not dependencies.node(name): + unpack.revert(name) + return False + except Exception as e: + unpack.revert(name) + raise e priority.add_new_plugin(name, 2) handler.load(name) diff --git a/src/backend/plugins/unpack.py b/src/backend/plugins/unpack.py index 46ac604..45bf05c 100644 --- a/src/backend/plugins/unpack.py +++ b/src/backend/plugins/unpack.py @@ -13,17 +13,17 @@ import zipfile -def unzip(name: str) -> bool: - _clear_prev_installation(name) - with zipfile.ZipFile(f'{PLUGINS_DOWNLOAD}/{name}.zip', 'r') as zip_ref: - zip_ref.extractall(f'{PLUGINS_DIR}/{name}') +def unzip(plugin_name: str) -> bool: + _clear_prev_installation(plugin_name) + with zipfile.ZipFile(f'{PLUGINS_DOWNLOAD}/{plugin_name}.zip', 'r') as zip_ref: + zip_ref.extractall(f'{PLUGINS_DIR}/{plugin_name}') return True -def _clear_prev_installation(name: str) -> None: - if os.path.exists(f'{PLUGINS_DIR}/{name}'): - shutil.rmtree(f'{PLUGINS_DIR}/{name}') +def _clear_prev_installation(plugin_name: str) -> None: + if os.path.exists(f'{PLUGINS_DIR}/{plugin_name}'): + shutil.rmtree(f'{PLUGINS_DIR}/{plugin_name}') def _find_target_file(root_dir: str, target: str) -> Optional[str]: @@ -67,19 +67,28 @@ def distribute(plugin_name: str) -> bool: _clear_frontend_installation(plugin_name) root_dir = f'{PLUGINS_DIR}/{plugin_name}' if os.path.exists(f'{root_dir}/frontend'): - shutil.move(f'{root_dir}/frontend', f'{FRONTEND_PLUGINS_DIR}/{plugin_name}') + shutil.move(f'{root_dir}/frontend', + f'{FRONTEND_PLUGINS_DIR}/{plugin_name}') else: - logging.warning(f'Frontend directorty of plugin "{plugin_name}" does not exist') + logging.warning(f'Frontend directorty of plugin "{ + plugin_name}" does not exist') if os.path.exists(f'{root_dir}/pages'): shutil.move(f'{root_dir}/pages', f'{FRONTEND_PAGES_DIR}/{plugin_name}') else: - logging.warning(f'Pages directory of plugin "{plugin_name}" does not exist') + logging.warning(f'Pages directory of plugin "{ + plugin_name}" does not exist') return True -def _clear_frontend_installation(name: str) -> None: - if os.path.exists(f'{FRONTEND_PLUGINS_DIR}/{name}'): - shutil.rmtree(f'{FRONTEND_PLUGINS_DIR}/{name}') - if os.path.exists(f'{FRONTEND_PAGES_DIR}/{name}'): - shutil.rmtree(f'{FRONTEND_PAGES_DIR}/{name}') +def _clear_frontend_installation(plugin_name: str) -> None: + if os.path.exists(f'{FRONTEND_PLUGINS_DIR}/{plugin_name}'): + shutil.rmtree(f'{FRONTEND_PLUGINS_DIR}/{plugin_name}') + if os.path.exists(f'{FRONTEND_PAGES_DIR}/{plugin_name}'): + shutil.rmtree(f'{FRONTEND_PAGES_DIR}/{plugin_name}') + + +def revert(plugin_name: str) -> None: + logging.info(f'Reverting installation of plugin "{plugin_name}"') + _clear_prev_installation(plugin_name) + _clear_frontend_installation(plugin_name)