Linux evo.fastest-server.com 5.14.0-284.1101.el9.tuxcare.11.els11.x86_64 #1 SMP PREEMPT_DYNAMIC Fri Aug 14 13:30:35 UTC 2026 x86_64
LiteSpeed
Server IP : 103.249.112.113 & Your IP : 216.73.217.135
Domains : 988 Domain
User : tanishks
Terminal
Auto Root
Create File
Create Folder
Localroot Suggester
Backdoor Destroyer
Readme
/
usr /
share /
lvemanager-xray /
plugins /
Delete
Unzip
Name
Size
Permission
Date
Action
cpanel
[ DIR ]
drwxr-xr-x
2026-09-04 11:37
directadmin
[ DIR ]
drwxr-xr-x
2026-09-04 11:37
plesk
[ DIR ]
drwxr-xr-x
2026-09-04 11:37
tests
[ DIR ]
drwxr-xr-x
2026-09-04 11:37
install-xray-plugin.py
36.21
KB
-rwxr-xr-x
2026-08-11 11:44
Save
Rename
#!/opt/cloudlinux/venv/bin/python3 -bb # coding:utf-8 # Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2021 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENSE.TXT from __future__ import print_function from __future__ import division from __future__ import absolute_import import grp import os import pwd import re import sys import json import shlex import stat import argparse import subprocess import configparser import cldetectlib as detect from clcommon.utils import get_rhn_systemid_value from clcommon.lib.cledition import is_cl_solo_edition from clcommon.ui_config import UIConfig SOURCE_PATH_BASE = "/usr/share/lvemanager-xray/" SOURCE_PATH_ADMIN = "/usr/share/lvemanager-xray/xray-admin/" SOURCE_PATH_USER = "/usr/share/lvemanager-xray/xray-user/" LVEMANAGER_SOURCE_PATH = "/usr/share/l.v.e-manager/" XRAY_MANAGER_UTILITY = "/usr/sbin/cloudlinux-xray-manager" DYNAMICUI_SYNC_CONFIG_COMMAND = "/usr/share/l.v.e-manager/utils/dynamicui.py --sync-conf=all --silent" CLOUDLINUX_CONFIG_COMMAND = ( "/usr/sbin/cloudlinux-config set --data '{\"options\": {\"uiSettings\": {\"hideXrayApp\": true}}}' --json" ) PANEL_INTEGRATION_CONFIG = '/opt/cpvendor/etc/integration.ini' CLOUDLINUX_TRANSLATIONS_DIR = '/usr/share/cloudlinux-translations/' CHECK_PLUGIN_CRON = { "name": "xray-plugin-check-cron", "schedule": "*/10 * * * *", "executor": "root", "command": "/usr/bin/flock -n /var/run/cloudlinux-xray-plugin-check.cronlock /usr/share/lvemanager-xray/plugins/install-xray-plugin.py --check > /dev/null 2>&1", } class Base: def __init__(self): pass def add_cron(self, file_name, schedule, executor, command): """ Create cron job :param file_name: cron name :param schedule: schedule of execution like "* * * * *" :param executor: user to run cron :param command: command to execute :return: None """ try: f = open('/etc/cron.d/' + file_name, 'w') f.write("{} {} {}\n".format(schedule, executor, command)) f.close() except Exception as e: print('Cannot add cron job. {}'.format(e)) def remove_cron(self, file_name): """ Remove cron :param file_name: cron name :return: None """ try: if file_name: os.remove('/etc/cron.d/' + file_name) except Exception as e: print('Cannot remove cron job. {}'.format(e)) def parse_command(self, command): """ Parses a command string into a list of arguments. """ if isinstance(command, str): if command.strip() == "": return [] return shlex.split(command) elif isinstance(command, list): return command else: return [] def exec_command(self, command, env=None): """ This function will run passed command in command line and returns result :param command: :param env: :return: """ result = [] try: args = self.parse_command(command) if not args: raise ValueError(f"The provided command is not valid: {command}") p = subprocess.Popen(args, stdout=subprocess.PIPE, env=env, text=True) while 1: output = p.stdout.readline() if not output: break if output.strip() != "": result.append(output.strip()) except Exception as e: print("Call process error: " + str(e)) return result def disable_agent(self): system_id = self.get_system_id() if system_id is None: print("Warning: Cannot disable user-agent because system_id is None.") return self.exec_command([XRAY_MANAGER_UTILITY, "disable-user-agent", "--system_id", system_id]) def enable_agent(self): system_id = self.get_system_id() if system_id is None: print("Warning: Cannot enable user-agent because system_id is None.") return self.exec_command([XRAY_MANAGER_UTILITY, "enable-user-agent", "--system_id", system_id]) def is_agent_enabled_in_config(self): return UIConfig().get_param('hideXrayApp', 'uiSettings') is False def sync_ui_config(self): self.exec_command(DYNAMICUI_SYNC_CONFIG_COMMAND) def configure_plugin(self): self.sync_ui_config() if self.is_agent_enabled_in_config(): self.enable_agent() # Automatic disable was turned off because we still need # xray-user-agent to work in background for smart advice # previously this caused issues when this script was triggered in upcp # we may revisit this in the future # else: # self.disable_agent() def get_system_id(self): try: system_id = get_rhn_systemid_value('system_id').split('-')[1] return system_id except Exception: print("Warning: Cannot get system id. It looks like your system is not registered.") return def reset_ui_config(self): if not is_cl_solo_edition(skip_jwt_check=True): self.exec_command(CLOUDLINUX_CONFIG_COMMAND) self.exec_command(DYNAMICUI_SYNC_CONFIG_COMMAND) def remove_file(self, filename): try: os.remove(filename) except OSError: pass def is_agent_running(self): """ Check if user agent running :return: Boolean """ try: system_id = self.get_system_id() if system_id is None: return False result = json.loads( self.exec_command([XRAY_MANAGER_UTILITY, "user-agent-status", "--system_id", system_id])[0] ) return result["status"] == "enabled" except Exception as e: print('Cannot get agent status. {}'.format(e)) return False def check_and_repair_plugin(self): """ Check if there is misconfiguration between UiConfig and agent service and reconfigure plugin :return: None """ if self.is_agent_enabled_in_config() and not self.is_agent_running(): self.configure_plugin() def _open_dir_path(self, path, create=False): """ Open a directory path without following symlinks in any component. ``O_NOFOLLOW`` on a complete path protects only its final component. Walking from ``/`` (or ``.`` for a relative path) via dir-fd-bound ``openat`` calls keeps every component tied to the directory inode that was validated. When ``create`` is true, missing components are created relative to that same validated fd and then opened with ``O_NOFOLLOW``. """ absolute = os.path.isabs(path) path = path.rstrip("/") parts = [part for part in path.split(os.sep) if part and part != "."] if ".." in parts: raise ValueError("parent directory components are not allowed") flags = os.O_RDONLY | os.O_NOFOLLOW | os.O_DIRECTORY current_fd = os.open(os.sep if absolute else ".", flags) try: for part in parts: try: next_fd = os.open(part, flags, dir_fd=current_fd) except FileNotFoundError: if not create: raise try: os.mkdir(part, dir_fd=current_fd) except FileExistsError: # A concurrent creator won the race; the no-following # open below still validates what appeared. pass next_fd = os.open(part, flags, dir_fd=current_fd) os.close(current_fd) current_fd = next_fd return current_fd except Exception: os.close(current_fd) raise def copy_file_or_dir(self, src, dst): """ Copy file or directory to specified location. Resolves every component of `dst` via dir-fd-bound O_NOFOLLOW opens so the remove and subsequent create are bound to validated directory inodes. This rejects both an intermediate symlink and a leaf symlink planted in a writable destination tree. """ try: # Strip trailing slashes: O_NOFOLLOW is silently ignored when the # path ends with `/` because the kernel resolves the trailing # slash as a directory dereference of the final component, # following a symlink substituted at the basename instead of # rejecting it. dst = dst.rstrip("/") parent, name = os.path.split(dst) if not name: print("An error occurred while copying: invalid destination {}".format(dst)) return # Create and open the parent component-by-component. This preserves # first-install behaviour while rejecting a symlink substituted at # any ancestor, not only at the final parent component. parent_fd = self._open_dir_path(parent, create=True) except Exception as e: print("An error occurred while copying: {}".format(e)) return try: if os.path.isfile(src): self._unlink_at(parent_fd, name) self._copy_file_at(src, parent_fd, name) elif os.path.isdir(src): self._unlink_at(parent_fd, name) self._copy_tree_at(src, parent_fd, name) except Exception as e: print("An error occurred while copying: {}".format(e)) finally: try: os.close(parent_fd) except OSError: pass def _unlink_at(self, dir_fd, name): """ Remove `name` under `dir_fd` if it exists, without following symlinks out of the parent. A symlink at `name` is unlinked as the link itself (os.unlink with follow_symlinks=False / fd-relative semantics). """ # Probe the entry with lstat-equivalent fstatat: do not follow # symlinks here, otherwise an attacker-planted symlink would steer # us into a tree we then proceed to rmtree. try: st = os.stat(name, dir_fd=dir_fd, follow_symlinks=False) except FileNotFoundError: return except OSError as e: print("An error occurred while removing {}: {}".format(name, e)) return try: if stat.S_ISDIR(st.st_mode): # Recursively unlink contents bound to a fd of the subtree, # then rmdir the entry via the parent fd. shutil.rmtree on a # path would re-resolve through the attacker-writable parent. self._rmtree_at(dir_fd, name) else: os.unlink(name, dir_fd=dir_fd) except OSError as e: print("An error occurred while removing {}: {}".format(name, e)) def _rmtree_at(self, parent_fd, name): """ Recursively remove the directory `name` rooted at `parent_fd`. Opens the subtree with O_NOFOLLOW | O_DIRECTORY (rejecting a symlink substituted at `name`), walks via os.fwalk in bottom-up mode so each dirent is unlinked relative to its live dir fd, then rmdirs `name` from `parent_fd`. No path is ever re-resolved through the attacker-writable parent during the walk. """ top_fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_DIRECTORY, dir_fd=parent_fd) try: for _dp, dirnames, filenames, sub_fd in os.fwalk( top=".", dir_fd=top_fd, topdown=False, follow_symlinks=False ): for f in filenames: try: os.unlink(f, dir_fd=sub_fd) except OSError as e: print("An error occurred while removing {}: {}".format(f, e)) for d in dirnames: # With follow_symlinks=False, fwalk classifies a # symlink-pointing-at-a-directory into `dirnames`. # os.rmdir refuses symlinks (ENOTDIR), so probe with # fstatat-no-follow first and unlink-instead when the # entry is really a symlink — otherwise rmdir is left # holding a non-empty directory and the final # rmdir(name, parent_fd) below fails, silently breaking # the install/upgrade. try: st = os.stat(d, dir_fd=sub_fd, follow_symlinks=False) if stat.S_ISLNK(st.st_mode): os.unlink(d, dir_fd=sub_fd) else: os.rmdir(d, dir_fd=sub_fd) except OSError as e: print("An error occurred while removing {}: {}".format(d, e)) finally: os.close(top_fd) os.rmdir(name, dir_fd=parent_fd) def _copy_file_at(self, src, dir_fd, name): """ Copy regular-file `src` to `name` under `dir_fd`. The destination is created via openat with O_CREAT | O_EXCL | O_NOFOLLOW so a symlink planted at `name` after the unlink (or before, if `name` did not previously exist) causes the open to fail with EEXIST / ELOOP rather than have root write into the attacker-chosen target. """ src_fd = os.open(src, os.O_RDONLY) try: src_st = os.fstat(src_fd) mode = src_st.st_mode & 0o777 dst_fd = os.open(name, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, mode, dir_fd=dir_fd) try: while True: buf = os.read(src_fd, 65536) if not buf: break while buf: n = os.write(dst_fd, buf) buf = buf[n:] # umask: O_CREAT applies `mode & ~umask`. fchmod the # already-open fd to restore the source's exact mode so a # restrictive umask (077) does not strip web-asset bits. os.fchmod(dst_fd, mode) finally: os.close(dst_fd) finally: os.close(src_fd) def _copy_tree_at(self, src, parent_fd, name): """ Replicate the directory `src` as `name` under `parent_fd`. Mirrors the leaf-by-leaf shape of shutil.copytree but every destination open/mkdir is relative to a dir fd descended from `parent_fd` via O_NOFOLLOW | O_DIRECTORY, so an attacker cannot redirect a leaf write by planting a symlink in any sub-directory we just created. """ # Preserve the source directory's mode (matches shutil.copytree's # copystat behaviour) — under a restrictive umask 0o755 would be # masked to 0o700, breaking web-asset accessibility. Read the source # mode now so we can apply it after mkdir. src_top_mode = os.stat(src, follow_symlinks=True).st_mode & 0o777 # Atomically claim the destination name. If a symlink or any other # entry races into place after the unlink, mkdir fails with EEXIST. os.mkdir(name, mode=src_top_mode, dir_fd=parent_fd) top_fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_DIRECTORY, dir_fd=parent_fd) try: # Restore exact source mode via fchmod — defends against umask # masking the mkdir mode (mkdir applies `mode & ~umask`). # Placed INSIDE the try so a failure here closes top_fd via # the finally below. os.fchmod(top_fd, src_top_mode) # follow_symlinks=True for the source walk so symlink-to-directory # entries are descended into and replicated (matches # shutil.copytree's default symlinks=False semantics). The source # tree is the package-shipped payload (root-owned), so following # links there is safe; destination-side O_NOFOLLOW is preserved # below — that's where the actual TOCTOU threat lives. for dirpath, dirnames, filenames, sub_fd in os.fwalk(top=src, follow_symlinks=True): rel = os.path.relpath(dirpath, src) # Descend the destination tree in lock-step with the source # walk, opening every level under top_fd via O_NOFOLLOW so # the descent is fd-bound. if rel == ".": dst_sub_fd = top_fd close_dst_sub = False else: dst_sub_fd = self._open_under(top_fd, rel) close_dst_sub = True try: for d in dirnames: # Preserve source directory mode (matches # shutil.copytree's copystat). Read src dir mode # before mkdir so we apply it consistently — and # protect against umask masking via fchmod after. src_d_mode = os.stat(d, dir_fd=sub_fd, follow_symlinks=True).st_mode & 0o777 try: os.mkdir(d, mode=src_d_mode, dir_fd=dst_sub_fd) except FileExistsError: # A prior leaf walk created this; harmless. pass # Re-apply the mode via fchmod through an O_NOFOLLOW # openat — the dst parent dir is attacker-writable # post-chown, so O_NOFOLLOW here protects against a # symlink swap, and fchmod overrides any umask. d_fd = os.open(d, os.O_RDONLY | os.O_NOFOLLOW | os.O_DIRECTORY, dir_fd=dst_sub_fd) try: os.fchmod(d_fd, src_d_mode) finally: os.close(d_fd) for f in filenames: # Source side is the package-shipped tree (root-owned, # not attacker-writable): no symlink-protection needed # here. Dropping O_NOFOLLOW matches shutil.copytree's # default symlinks=False behaviour (dereference src # symlinks, copy their target content) — with # O_NOFOLLOW any symlink-to-file in the RPM payload # would raise ELOOP and abort the whole copy mid-way. src_leaf_fd = os.open(f, os.O_RDONLY, dir_fd=sub_fd) try: st = os.fstat(src_leaf_fd) leaf_mode = st.st_mode & 0o777 dst_leaf_fd = os.open( f, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, leaf_mode, dir_fd=dst_sub_fd ) try: while True: buf = os.read(src_leaf_fd, 65536) if not buf: break while buf: n = os.write(dst_leaf_fd, buf) buf = buf[n:] # umask: see _copy_file_at — fchmod the # already-open fd to restore the source's # exact mode. os.fchmod(dst_leaf_fd, leaf_mode) finally: os.close(dst_leaf_fd) finally: os.close(src_leaf_fd) finally: if close_dst_sub: try: os.close(dst_sub_fd) except OSError: pass finally: try: os.close(top_fd) except OSError: pass def _open_under(self, top_fd, rel): """ Open `rel` (a forward-slash-separated relative path that os.fwalk produced) under `top_fd`, descending one component at a time with O_NOFOLLOW | O_DIRECTORY so no intermediate symlink can redirect us. """ cur_fd = top_fd opened = [] try: for part in rel.split(os.sep): if not part or part == ".": continue nxt = os.open(part, os.O_RDONLY | os.O_NOFOLLOW | os.O_DIRECTORY, dir_fd=cur_fd) opened.append(nxt) cur_fd = nxt # Caller owns the final fd; close every ancestor we opened. for fd in opened[:-1]: try: os.close(fd) except OSError: pass return cur_fd except Exception: for fd in opened: try: os.close(fd) except OSError: pass raise def remove_file_or_dir(self, path): """ Remove file or directory. Resolves every parent component with dir-fd-bound O_NOFOLLOW opens so the unlink/rmtree is bound to validated directory inodes. A symlink at any ancestor or a swap at `path` cannot redirect the removal. """ try: path = path.rstrip("/") parent, name = os.path.split(path) if not name: return parent_fd = self._open_dir_path(parent) except FileNotFoundError: return except Exception as e: print("An error occurred while copying: {}".format(e)) return try: self._unlink_at(parent_fd, name) except Exception as e: print("An error occurred while copying: {}".format(e)) finally: try: os.close(parent_fd) except OSError: pass def safe_recursive_chown(self, path, user, group): """ Recursively change ownership without following symlinks. Uses openat/fwalk semantics via dir-fd so path resolution is bound to a specific directory inode for the whole walk — closes the TOCTOU window between realpath() and os.walk() that a plain realpath+walk approach leaves open (os.walk follows its top argument even when followlinks=False, and os.lchown only protects the final component). """ try: # pwd.getpwnam / grp.getgrnam raise KeyError on missing entries; # catch Exception (same semantic as the replaced exec_command # helper) so a missing user/group logs and continues rather # than aborting the whole %posttrans install flow. uid = pwd.getpwnam(user).pw_uid gid = grp.getgrnam(group).gr_gid top_fd = self._open_dir_path(path) except Exception as e: print("An error occurred while opening path for chown: {}".format(e)) return try: # Chown the top directory itself via its fd — immune to rename. os.fchown(top_fd, uid, gid) # Walk via file descriptors. os.fwalk uses openat internally, so # descents are relative to a live dir fd and cannot be redirected # by an intermediate symlink swap mid-walk. for _dirpath, dirnames, filenames, sub_fd in os.fwalk(top=".", dir_fd=top_fd, follow_symlinks=False): for name in dirnames + filenames: # Per-entry try/except so a single ENOENT / EPERM on a # mid-walk race doesn't abort the whole traversal — # matches the prior `chown -R` shell resilience. try: os.chown(name, uid, gid, dir_fd=sub_fd, follow_symlinks=False) except OSError as e: print("An error occurred while chowning {}: {}".format(name, e)) except Exception as e: print("An error occurred while changing ownership: {}".format(e)) finally: try: os.close(top_fd) except OSError: pass def safe_chmod_nofollow(self, path, mode, dir_fd=None): """ Change permissions on a path without following symlinks. When called with a dir_fd, path is resolved relative to the directory fd via openat() — used by safe_recursive_chmod to bind each leaf operation to the live dir fd produced by os.fwalk. O_NONBLOCK guards against a racing FIFO at that entry: without it, os.open(O_RDONLY) on a FIFO blocks forever waiting for a writer and hangs the root-privileged installer (the replaced `chmod -R` used fchmodat semantics that never opened the file). """ try: fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=dir_fd) try: os.fchmod(fd, mode) finally: os.close(fd) except Exception as e: print("An error occurred while changing permissions: {}".format(e)) def safe_recursive_chmod(self, path, mode): """ Recursively change permissions without following symlinks. Same openat/fwalk discipline as safe_recursive_chown — path resolution is bound to a dir fd so a concurrent directory->symlink swap between the initial open and the recursive traversal cannot redirect us to an attacker-chosen target. """ try: top_fd = self._open_dir_path(path) except Exception as e: print("An error occurred while opening path for chmod: {}".format(e)) return try: os.fchmod(top_fd, mode) for _dirpath, dirnames, filenames, sub_fd in os.fwalk(top=".", dir_fd=top_fd, follow_symlinks=False): for name in dirnames + filenames: self.safe_chmod_nofollow(name, mode, dir_fd=sub_fd) except Exception as e: print("An error occurred while changing permissions: {}".format(e)) finally: try: os.close(top_fd) except OSError: pass def generate_translate_templates(self): """ Prepare translate templates using english as a base. """ source_path = f"{SOURCE_PATH_USER}i18n/en-en.json" template_path = f"{CLOUDLINUX_TRANSLATIONS_DIR}xray-user-ui.json" self.copy_file_or_dir(source_path, template_path) class CpanelPluginInstaller(Base): def __init__(self): self.ROOT_CPANEL_DIR = "/usr/local/cpanel/whostmgr/docroot/" self.CPANEL_THEMES_BASE_DIR = "/usr/local/cpanel/base/frontend/" self.template_src = SOURCE_PATH_BASE + "plugins/cpanel/xray.live.pl" # template_dst should be format with theme name and placed for each theme self.template_dst = self.CPANEL_THEMES_BASE_DIR + "{}/lveversion/xray.live.pl" self.plugin_tar = SOURCE_PATH_BASE + "plugins/cpanel/cpanel-xray-plugin.tar.bz2" self.plugin_installer = "/usr/local/cpanel/scripts/install_plugin" self.plugin_uninstaller = "/usr/local/cpanel/scripts/uninstall_plugin" self.xray_feature_file = "/usr/local/cpanel/whostmgr/addonfeatures/lvexray" self.install_config = SOURCE_PATH_BASE + "plugins/cpanel/plugin/install.json" self.destination_admin = "/usr/local/cpanel/whostmgr/docroot/3rdparty/cloudlinux/assets/xray-admin" self.destination_user = "/usr/local/cpanel/whostmgr/docroot/3rdparty/cloudlinux/assets/xray-user" def cpanel_fix_feature_manager(self): if os.path.exists(self.xray_feature_file) and os.path.exists(self.install_config): with open(self.install_config) as install_config: feature_config = json.load(install_config) feature = feature_config[0] # We have only one item try: feature_name_fixed = re.search("\$LANG{'(.*)'}", feature['name']).group(1) except AttributeError: feature_name_fixed = '' feature_file = open(self.xray_feature_file, 'w') feature_file.write(feature['feature'] + ':' + feature_name_fixed) feature_file.close() def install_plugin(self): self.copy_file_or_dir(SOURCE_PATH_ADMIN, self.destination_admin) self.copy_file_or_dir(SOURCE_PATH_USER, self.destination_user) for theme in self.get_theme_list(): self.copy_file_or_dir(self.template_src, self.template_dst.format(theme)) self.exec_command([self.plugin_installer, self.plugin_tar, "--theme", theme]) self.cpanel_fix_feature_manager() def uninstall_plugin(self): self.remove_file_or_dir(self.destination_admin) self.remove_file_or_dir(self.destination_user) for theme in self.get_theme_list(): self.remove_file_or_dir(self.template_dst.format(theme)) self.exec_command([self.plugin_uninstaller, self.plugin_tar, "--theme", theme]) def get_theme_list(self): if os.path.isdir(self.CPANEL_THEMES_BASE_DIR): return next(os.walk(self.CPANEL_THEMES_BASE_DIR), (None, None, []))[1] class PleskPluginInstaller(Base): def __init__(self): self.ROOT_PLESK_DIR = "/usr/local/psa/admin/" self.controller_src = SOURCE_PATH_BASE + "plugins/plesk/XrayController.php" self.controller_dst = self.ROOT_PLESK_DIR + "plib/modules/plesk-lvemanager/controllers/XrayController.php" self.send_request_controller_src = SOURCE_PATH_BASE + "plugins/plesk/XraySendRequestController.php" self.send_request_controller_dst = ( self.ROOT_PLESK_DIR + "plib/modules/plesk-lvemanager/controllers/XraySendRequestController.php" ) self.destination_admin = "/usr/local/psa/admin/htdocs/modules/plesk-lvemanager/xray-admin" self.destination_user = "/usr/local/psa/admin/htdocs/modules/plesk-lvemanager/xray-user" self.icon_src = SOURCE_PATH_BASE + "plugins/plesk/xray.svg" self.icon_dst = self.ROOT_PLESK_DIR + "htdocs/modules/plesk-lvemanager/images/xray.svg" self.views_dir = self.ROOT_PLESK_DIR + "plib/modules/plesk-lvemanager/views/scripts/xray/" self.template_src = SOURCE_PATH_BASE + "plugins/plesk/index.phtml" self.template_dst = self.ROOT_PLESK_DIR + "plib/modules/plesk-lvemanager/views/scripts/xray/index.phtml" def install_plugin(self): self.copy_file_or_dir(SOURCE_PATH_ADMIN, self.destination_admin) self.copy_file_or_dir(SOURCE_PATH_USER, self.destination_user) self.copy_file_or_dir(self.controller_src, self.controller_dst) self.copy_file_or_dir(self.send_request_controller_src, self.send_request_controller_dst) self.copy_file_or_dir(self.icon_src, self.icon_dst) if not os.path.isdir(self.views_dir): os.mkdir(self.views_dir) self.copy_file_or_dir(self.template_src, self.template_dst) def uninstall_plugin(self): self.remove_file_or_dir(self.destination_admin) self.remove_file_or_dir(self.destination_user) self.remove_file_or_dir(self.controller_dst) self.remove_file_or_dir(self.send_request_controller_dst) self.remove_file_or_dir(self.icon_dst) self.remove_file_or_dir(self.views_dir) class DirectAdminPluginInstaller(Base): def __init__(self): # Plugin files self.source_user_plugin = SOURCE_PATH_BASE + "plugins/directadmin/xray/" self.destination_user_plugin = "/usr/local/directadmin/plugins/xray/" self.source_index_file = SOURCE_PATH_BASE + "plugins/directadmin/xrayIndex.php" self.destination_index_file = "/usr/local/directadmin/plugins/lvemanager_spa/app/View/Spa/index/xrayIndex.php" self.plugin_conf_file = self.destination_user_plugin + "/plugin.conf" self.destination_admin_spa = "/usr/local/directadmin/plugins/lvemanager_spa/images/assets/xray-admin" self.destination_user_spa = "/usr/local/directadmin/plugins/xray/images/xray-user" def install_plugin(self): self.copy_file_or_dir(SOURCE_PATH_ADMIN, self.destination_admin_spa) self.copy_file_or_dir(self.source_user_plugin, self.destination_user_plugin) self.copy_file_or_dir(self.source_index_file, self.destination_index_file) self.copy_file_or_dir(SOURCE_PATH_USER, self.destination_user_spa) self.safe_recursive_chown(self.destination_user_plugin, "diradmin", "diradmin") self.safe_recursive_chown(self.destination_admin_spa, "diradmin", "diradmin") self.safe_recursive_chmod(self.destination_user_plugin, 0o755) self.safe_chmod_nofollow(self.plugin_conf_file, 0o644) def uninstall_plugin(self): self.remove_file_or_dir(self.destination_user_plugin) self.remove_file_or_dir(self.destination_admin_spa) class PanelIntegrationPluginInstaller(Base): def __init__(self): self.destination_admin_py_plugin = "/usr/share/l.v.e-manager/commons/spa-resources/static/xray-admin" self.destination_user_py_plugin = "/usr/share/l.v.e-manager/commons/spa-resources/static/xray-user" def get_panel_base_path(self): try: parser = configparser.ConfigParser(interpolation=None, strict=False) parser.read(PANEL_INTEGRATION_CONFIG) base_path = parser.get("lvemanager_config", "base_path") return base_path except Exception as e: print('Cannot copy files for no panel version. {}'.format(e)) sys.exit(1) def install_plugin(self): base_path = self.get_panel_base_path().rstrip('/') self.copy_file_or_dir(SOURCE_PATH_ADMIN, self.destination_admin_py_plugin) self.copy_file_or_dir(SOURCE_PATH_USER, self.destination_user_py_plugin) self.copy_file_or_dir(SOURCE_PATH_ADMIN, base_path + '/assets/xray-admin') self.copy_file_or_dir(SOURCE_PATH_USER, base_path + '/assets/xray-user') def uninstall_plugin(self): base_path = self.get_panel_base_path().rstrip('/') self.remove_file_or_dir(self.destination_admin_py_plugin) self.remove_file_or_dir(self.destination_user_py_plugin) self.remove_file_or_dir(base_path + '/assets/xray-admin') self.remove_file_or_dir(base_path + '/assets/xray-user') class Main: def __init__(self): pass def make_parser(self): parser = argparse.ArgumentParser(description="Script to install|uninstall Xray App for user") parser.add_argument("--install", "-i", action="store_true", help="Install Xray App") parser.add_argument("--uninstall", "-u", action="store_true", help="Uninstall Xray App") parser.add_argument("--check", "-c", action="store_true", help="Check and repair plugin") return parser def run(self): parser = self.make_parser() args = parser.parse_args() if detect.is_cpanel(): # Cpanel installer = CpanelPluginInstaller() elif detect.is_da(): # DirectAdmin installer = DirectAdminPluginInstaller() elif detect.is_plesk(): # Plesk installer = PleskPluginInstaller() elif os.path.isfile(PANEL_INTEGRATION_CONFIG): # Custom panel with integration.ini installer = PanelIntegrationPluginInstaller() else: print("X-Ray plugin cannot be installed on your environment") sys.exit(0) if args.install: installer.install_plugin() installer.generate_translate_templates() installer.configure_plugin() installer.add_cron( CHECK_PLUGIN_CRON["name"], CHECK_PLUGIN_CRON["schedule"], CHECK_PLUGIN_CRON["executor"], CHECK_PLUGIN_CRON["command"], ) elif args.uninstall: installer.remove_cron(CHECK_PLUGIN_CRON["name"]) installer.uninstall_plugin() installer.reset_ui_config() elif args.check: installer.check_and_repair_plugin() else: parser.print_help() if __name__ == "__main__": main = Main() main.run()