__init__.py000064400000000115147205401360006654 0ustar00from pathlib import Path SCRIPT_BASEPATH = Path(__file__).parent.resolve() cpanel.py000064400000022534147205401360006370 0ustar00from typing import Dict, Any, List, Tuple import logging from customer_local_ops import Ops, OpType, ResourceType from customer_local_ops.util.retry import retry LOG = logging.getLogger(__name__) RETRY_CPANEL_ACTIVATE_TIMEOUT = 15 * 60 # seconds # pylint: disable=invalid-name RETRY_CPANEL_ACTIVATE_RETRY_INTERVAL = 20 # seconds # pylint: disable=invalid-name class OSCPanel: """Mixin class representing common functionality and constants for CPanel between all OSes""" OP_CPANEL_ACTIVATE = 'cpanel_activate' RUN_INSTALLATRON_REPAIR = 'run_installatron_repair' CPANEL_OPS_RESOURCE_ATTRIBUTE_MAP = { ResourceType.OPENSTACK: { OP_CPANEL_ACTIVATE: { RUN_INSTALLATRON_REPAIR: False, }, }, ResourceType.OVH: { OP_CPANEL_ACTIVATE: { RUN_INSTALLATRON_REPAIR: False, }, }, ResourceType.VIRTUOZZO_VM: { OP_CPANEL_ACTIVATE: { RUN_INSTALLATRON_REPAIR: False, }, }, ResourceType.OPENSTACK_HOSTINGCLOUD: { OP_CPANEL_ACTIVATE: { RUN_INSTALLATRON_REPAIR: False, }, } } class CPanel(Ops): """Customer Local ops for CPanel""" op_type = OpType.CONTROL_PANEL def configure_mta(self, payload: Dict[str, Any], *args: Any) -> Tuple[bool, Dict[str, Any]]: """Configures the mail transfer agent for cPanel. This is a bit different. If the user picks cpanel, we don't want to do the regular os_op configureMTA (exim conflicts with sendmail) :param payload: A dict containing input data """ LOG.info("Cpanel.configure_mta start") op_name = 'configure_mta' cp_os_op_instance = self.get_os_op(payload['os_op']) os_result = cp_os_op_instance.configure_mta_cpanel(payload) return self.build_result_from_other_result(os_result, op_name) def change_password(self, payload: Dict[str, Any], *args: Any) -> Any: """Changes the user password via an op for the local operating system :param payload: A dict containing input data """ os_op = payload["os_op"] op_name = 'change_password' LOG.info("CPanel.change_password is NOOP, deferring to OS operation: %s.%s", str(os_op), op_name) os_op_instance = self.get_os_op(os_op) return os_op_instance.change_password(payload) @retry(interval=2, timeout=5*60) def change_hostname(self, payload: Dict[str, Any], *args: Any, intermediate_result: Dict[str, Any] = None) -> Any: """Changes the server hostname via an op for the local operating system :param payload: A dict containing input data :param intermediate_result: intermediate result for storing the cPanel set-hostname result while waiting for the lock file to clear """ LOG.info("CPanel.change_hostname start") os_op = payload["os_op"] op_name = 'change_hostname' LOG.info("CPanel.change_hostname deferring to OS operation: %s.%s", str(os_op), op_name) try: os_op_instance = self.get_os_op(os_op) except AttributeError as ex: return False, self.build_result_dict('', str(ex), op_name) if intermediate_result is None: os_result = os_op_instance.change_hostname(payload) data = self.get_result_data(os_result) LOG.info("CPanel.change_hostname os_op_result - %s - %s - %s", data.success, data.outs, data.errs) if not data.success: return self.build_result_from_other_result(os_result, op_name) # Perform operations on Control Panel os_result = os_op_instance.change_hostname_cpanel( payload['hostname'], intermediate_result=intermediate_result) return self.build_result_from_other_result(os_result, op_name) def get_public_ip(self, os_op: str, *args: Any) -> Any: """Gets the cPanel public IP for this server :param os_op: Operating system customer_local_ops class of target server """ op_name = 'get_public_ip' os_op_instance = self.get_os_op(os_op) os_result = os_op_instance.get_public_ip_cpanel() data = self.get_result_data(os_result) if not data.success: LOG.info("CPanel.get_public_ip os_op_result - %s - %s - %s", data.success, data.outs, data.errs) return self.build_result_from_other_result(os_result, op_name) return os_result def mark_internal_addresses(self, os_op: str, private_addrs: List[str], *args: Any) -> Tuple[bool, Dict]: """Marks the server IPs as reserved :param os_op: Operating system customer_local_ops class of target server :param private_addrs: A list of IPs to mark """ op_name = 'mark_internal_addresses' os_op_instance = self.get_os_op(os_op) os_result = os_op_instance.mark_internal_addresses_cpanel(private_addrs) return self.build_result_from_other_result(os_result, op_name) def cpanel_enable(self, os_op: str, cpanel_public_ip: str, *args: Any) -> Any: """Enables cPanel functionality for this server :param os_op: Operating system customer_local_ops class of target server :param cpanel_public_ip: The cPanel public IP for the server """ op_name = 'cpanel_enable' os_op_instance = self.get_os_op(os_op) os_result = os_op_instance.cpanel_enable(cpanel_public_ip) return self.build_result_from_other_result(os_result, op_name) @retry(interval=RETRY_CPANEL_ACTIVATE_RETRY_INTERVAL, timeout=RETRY_CPANEL_ACTIVATE_TIMEOUT) def cpanel_activate(self, os_op: str, vm_resource: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> Any: """Activates cPanel license for this server. If another process is running cpkeyclt, this one will wait and retry to ensure a successful licensing. :param os_op: Operating system customer_local_ops class of target server :param vm_resource: The resource name for the third-party hosting provider :param intermediate_result: an intermediate result """ op_name = 'cpanel_activate' os_op_instance = self.get_os_op(os_op) os_result = os_op_instance.cpanel_activate(vm_resource, intermediate_result=intermediate_result) return self.build_result_from_other_result(os_result, op_name) def set_mysql_password(self, os_op: str, *args: Any) -> Any: """Generates and sets a random mysql password for cPanel :param os_op: Operating system customer_local_ops class of target server """ op_name = 'set_mysql_password' os_op_instance = self.get_os_op(os_op) os_result = os_op_instance.set_mysql_password_cpanel() return self.build_result_from_other_result(os_result, op_name) def enable_secure_tmp(self, os_op: str, *args: Any) -> Any: """Re-secures the /tmp directory :param os_op: Operating system customer_local_ops class of target server """ op_name = 'enable_secure_tmp' os_op_instance = self.get_os_op(os_op) os_result = os_op_instance.enable_secure_tmp_cpanel() return self.build_result_from_other_result(os_result, op_name) def cpanel_prep(self, os_op: str, *args: Any) -> Any: """Pre-installs and prepares cPanel on the server :param os_op: Operating system customer_local_ops class of target server """ op_name = 'cpanel_prep' os_op_instance = self.get_os_op(os_op) os_result = os_op_instance.cpanel_prep() return self.build_result_from_other_result(os_result, op_name) def hulk_whitelist(self, os_op: str, from_ip_addr: str, *args: Any) -> Tuple[bool, Dict[str, Any]]: """Allow cPanel access from customer IP :param os_op: Operating system customer_local_ops class of target server :param from_ip_addr: The IP address from which the customer will access the cPanel instance """ op_name = 'hulk_whitelist' os_op_instance = self.get_os_op(os_op) os_result = os_op_instance.hulk_whitelist_cpanel(from_ip_addr) return self.build_result_from_other_result(os_result, op_name) def get_hash(self, os_op: str, *args: Any) -> Any: """Set cPanel hash :param os_op: Operating system customer_local_ops class of target server """ op_name = 'get_hash' os_op_instance = self.get_os_op(os_op) os_result = os_op_instance.get_hash_cpanel() data = self.get_result_data(os_result) if not data.success: return self.build_result_from_other_result(os_result, op_name) return data.result def get_api_token(self, os_op: str, *args: Any) -> Any: """Get cPanel API Token :param os_op: Operating system customer_local_ops class of target server """ op_name = 'get_api_token' os_op_instance = self.get_os_op(os_op) os_result = os_op_instance.get_api_token_cpanel() data = self.get_result_data(os_result) if not data.success: return self.build_result_from_other_result(os_result, op_name) return data.result class CPanelException(Exception): def __init__(self, outs, errs): self.errs = errs self.outs = outs super().__init__(errs) ispconfig.py000064400000006172147205401360007107 0ustar00# -*- coding: utf-8 -*- from typing import Dict, Any, Tuple import logging from customer_local_ops import Ops, OpType LOG = logging.getLogger(__name__) class OSISPConfig: """Mixin class representing common functionality and constants for ISPConfig between all OSes""" class ISPConfig(Ops): op_type = OpType.CONTROL_PANEL os_op_instance = None def change_hostname(self, payload: Dict[str, Any], *args: Any) -> Any: """Changes the server hostname via an op for the local operating system and an op for ispconfig :param payload: A dict containing input data """ LOG.info("ISPConfig.change_hostname start") os_op = payload["os_op"] op_name = 'change_hostname' if self.os_op_instance is None: self.os_op_instance = self.get_os_op(os_op) LOG.info("ISPConfig.change_hostname deferring to OS operation: %s.%s", str( os_op), op_name) success, os_result = self._change_os_hostname(payload) if not success: return self.build_result_from_other_result((success, os_result), op_name) LOG.info("Change the hostname in ISPConfig.") self.os_op_instance.change_ispconfig_hostname(payload) return True, self.build_result_dict('hostname changed successfully', '', op_name) def _change_os_hostname(self, payload: Dict[str, Any]) -> Tuple[bool, Any]: """Changes the server hostname via an op for the local operating system :param payload: A dict containing input data """ os_result = self.os_op_instance.change_hostname(payload) data = self.get_result_data(os_result) LOG.info("ISPConfig.change_hostname os_op_result - %s - %s - %s", data.success, data.outs, data.errs) return data.as_tuple() def change_password(self, payload: Dict[str, Any], *args: Any) -> Any: """Changes the user password via an op for the local operating system and an op for ispconfig :param payload: A dict containing input data """ LOG.info("ISPConfig.change_password start") os_op = payload["os_op"] op_name = 'change_password' LOG.info("ISPConfig.change_password deferring to OS operation: %s.%s", str( os_op), op_name) if self.os_op_instance is None: self.os_op_instance = self.get_os_op(os_op) success, os_result = self._change_os_password(payload) if not success: return self.build_result_from_other_result((success, os_result), op_name) self.os_op_instance.change_ispconfig_password(payload, op_name) return True, self.build_result_dict('password changed successfully', '', op_name) def _change_os_password(self, payload: Dict[str, Any]) -> Tuple[bool, Any]: """Changes the user password via an op for the local operating system :param payload: A dict containing input data """ os_result = self.os_op_instance.change_password(payload) data = self.get_result_data(os_result) LOG.info("ISPConfig.change_password os_op_result - %s - %s - %s", data.success, data.outs, data.errs) return data.as_tuple() linux_cpanel.py000064400000053033147205401360007605 0ustar00from datetime import datetime from typing import Dict, Any, List, Tuple import functools import logging import os import socket import json from shortuuid import ShortUUID from customer_local_ops import OpType, ResourceType from customer_local_ops.operating_system.linux import ( AlmaLinux8, AlmaLinux9, Linux, CentOS, CentOS6, CentOS7, Debian, Debian8, Ubuntu1604) from customer_local_ops.control_panel.cpanel import CPanelException, OSCPanel from customer_local_ops.util import random_password from customer_local_ops.util.execute import runCommand, run_uapi_command from customer_local_ops.util.helpers import replace_line, edit_file_lines, create_file, replace_file_lines_multiple from customer_local_ops.util.retry import Retry, RETRY LOG = logging.getLogger(__name__) SHORT_UUID_DEFAULT_LENGTH = 30 class LinuxCPanel(Linux, OSCPanel): """ CPanel Customer Local Ops for the Linux OS. All function names should contain 'cpanel' so as not to override the OS ops """ AUTO_RESTART_EXCLUDE_SERVICES = ['nydus-ex', 'nydus-ex-api'] AUTO_RESTART_EXCLUSION_FILE = '/etc/cpanel/local/ignore_outdated_services' # ^ as of cPanel 11.76 / Jan 13 2019 HOSTNAME_CHANGE_LOCK_FILE = '/var/cpanel/.application-locks/UpdateHostname' op_type = OpType.CONTROL_PANEL_OPERATING_SYSTEM # this is a bit different. If the user picks cpanel, we don't want to do the regular os_op configureMTA # (exim conflicts with sendmail) def configure_mta_cpanel(self, payload: Dict[str, Any], *args: Any) -> Any: """Configures the mail transfer agent for cPanel :param payload: A dict containing input data """ LOG.info("LinuxCPanel.configure_mta_cpanel start") op_name = 'configure_mta_cpanel' try: set_tgt = functools.partial(replace_line, match='defaultmailaction', replace='defaultmailaction=fail\n', firstword=False) edit_file_lines('/var/cpanel/cpanel.config', set_tgt) relay = payload.get('relay_address') if relay is not None: exim_conf_path = '/etc/exim.conf.local' LOG.info("LinuxCPanel.configure_mta_cpanel writing %s", exim_conf_path) create_file(exim_conf_path, """ @AUTH@ @BEGINACL@ @CONFIG@ @DIRECTOREND@ @DIRECTORMIDDLE@ @DIRECTORSTART@ @ENDACL@ @RETRYEND@ @RETRYSTART@ @REWRITE@ @ROUTEREND@ @ROUTERSTART@ send_to_smart_host: driver = manualroute route_list = !+local_domains %s transport = remote_smtp @TRANSPORTEND@ @TRANSPORTMIDDLE@ @TRANSPORTSTART@ """ % relay) except Exception as ex: # pylint: disable=broad-except LOG.error('cp_os_op LinuxCPanel configure_mta_cpanel result(fail): %s', str(ex)) return False, self.build_result_dict('', str(ex), op_name) LOG.info("LinuxCPanel.configure_mta_cpanel running buildeximconf") return self._run_uapi_command(['/scripts/buildeximconf'], 'build exim config', op_name) def _run_uapi_command(self, cmd_list: List[str], description: str, op_name: str) -> Tuple[bool, Dict[str, Any]]: """Runs a local cPanel command :param cmd_list: A list of commands to run :param description: A description of the command(s) to be run :param op_name: The name of the op calling this function """ exit_code, outs, errs = run_uapi_command(cmd_list, description, op_name) if exit_code == 0: # cpanel's uapi returns 0 even when it errors. For example, if you try to add the same site name twice. LOG.debug("cp_op_result: success! %s", errs) if 'uapi --user=' in ' '.join(cmd_list): # The errors key is always returned. A '~' value indicates no errors occurred. if 'errors: ~' not in outs: return False, self.build_result_dict(outs, errs, op_name) if 'whmapi1 ' in ' '.join(cmd_list): # If result is 1, that denotes success. 0 denotes failure. if 'result: 1' not in outs: return False, self.build_result_dict(outs, errs, op_name) return True, self.build_result_dict(outs, errs, op_name) return False, self.build_result_dict(outs, errs, op_name) def change_hostname_cpanel(self, hostname: str, *_: Any, intermediate_result: Dict[str, Any] = None) -> Any: """Changes the server hostname via a command for the local operating system :param hostname: the new server hostname :param intermediate_result: Nydus intermediate result for storing the set-hostname result while waiting for the cPanel lock file to clear :returns: the result of the set-hostname cPanel call, in Nydus/CLO result format """ LOG.info("LinuxCPanel.change_hostname_cpanel start") if intermediate_result is None: result = self._run_uapi_command( ['/usr/local/cpanel/bin/set_hostname', hostname], 'set cpanel host', 'change_hostname_cpanel') if not result[0]: return result intermediate_result = {'set_hostname_result': result} # Wait for lock file to go if os.path.exists(self.HOSTNAME_CHANGE_LOCK_FILE): LOG.info('cPanel hostname change lock file exists (%s), will try again later.', self.HOSTNAME_CHANGE_LOCK_FILE) return Retry(intermediate_result=intermediate_result) return intermediate_result['set_hostname_result'] def get_public_ip_cpanel(self, *args: Any) -> Any: """Gets the cPanel public IP for this server""" op_name = 'get_public_ip_cpanel' command = ['wget', '-q', '-O', '-', 'http://www.cpanel.net/showip.cgi'] exit_code, outs, errs = runCommand(command, 'Get cPanel outbound IP') if exit_code == 0: public_ip = outs return public_ip return False, self.build_result_dict(outs, errs, op_name) def mark_internal_addresses_cpanel(self, private_addrs: List[str], *args: Any) -> Tuple[bool, Dict]: """Marks the server IPs as reserved :param private_addrs: A list of IPs to mark """ op_name = 'mark_internal_addresses_cpanel' try: create_file('/etc/reservedips', '\n'.join(private_addrs)) create_file('/etc/reservedipreasons', 'Internal datacenter-local addresses not publicly accessible.') except OSError as ex: return False, self.build_result_dict('', str(ex), op_name) return True, self.build_result_dict('Marking internal addresses succeeded', '', op_name) def cpanel_enable(self, cpanel_public_ip: str, *args: Any) -> Any: """Enables cPanel functionality for this server :param cpanel_public_ip: The cPanel public IP for the server """ op_name = 'cpanel_enable' LOG.debug("cpanel_public_ip- %s", cpanel_public_ip) # update /etc/wwwacct.conf ADDR try: edit_file_lines('/etc/wwwacct.conf', functools.partial(replace_line, match='ADDR', replace='ADDR %s\n' % cpanel_public_ip, firstword=True)) # update /var/cpanel/mainip try: os.remove('/var/cpanel/mainip') except OSError: pass create_file('/var/cpanel/mainip', '%s' % cpanel_public_ip) create_file('/var/cpanel/activate/2012-07.v01.EULACPWHM', '') create_file('/etc/.whostmgrft', '') exit_code, outs, errs = runCommand(['sed', '-i', 's/rpmup_allow_kernel=0/rpmup_allow_kernel=1/g', '/var/cpanel/cpanel.config'], 'enable automatic kernel updates') LOG.debug("Automatic kernel updates result: %s: %s -- %s", exit_code, errs, outs) exit_code, outs, errs = runCommand(['sed', '-i', 's/allow_deprecated_accesshash=0/allow_deprecated_accesshash=1/g', '/var/cpanel/cpanel.config'], 'enable accesshash') LOG.debug("Enable accesshash result: %s: %s -- %s", exit_code, errs, outs) exit_code, outs, errs = runCommand(['service', 'cpanel', 'restart'], 'restart cpanel') LOG.debug("restart cpanel result: %s: %s -- %s", exit_code, errs, outs) for feature in ('appconfig', 'email_archiving', 'email_autodiscovery', 'log_archiving', 'query_apache_for_nobody_senders', 'server_usage_analytics', 'servers_usage_analytics', 'smtp_restrictions', 'trust_x_php_script'): create_file(os.path.join('/var/cpanel/activate/features/', feature), """USER=root MODIFIED=%s TIMESTAMP=%s INTERFACE=GUI """ % (datetime.now().ctime(), datetime.now().ctime())) # The following section is an attempt to reduce queueprocd errors. exit_code, outs, errs = runCommand(['/scripts/restartsrv_queueprocd'], 'restart queueprocd') LOG.debug("Restart queueprocd result: %s: %s -- %s", exit_code, errs, outs) except OSError as ex: return False, self.build_result_dict('', str(ex), op_name) return True, self.build_result_dict('CPanel enabled successfully', '', op_name) def cpanel_activate(self, vm_resource: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> Any: """Activates cPanel license for this server. If another process is running cpkeyclt, this one will wait and retry to ensure a successful licensing. :param vm_resource: The resource name for the third-party hosting provider :param intermediate_result: an intermediate result """ op_name = self.OP_CPANEL_ACTIVATE exit_code, outs, errs = runCommand(['/usr/local/cpanel/cpkeyclt'], 'activate license') if 'A License check appears to already be running' in outs: return RETRY if exit_code != 0: return False, self.build_result_dict(outs, errs, op_name) ops_map = self.CPANEL_OPS_RESOURCE_ATTRIBUTE_MAP[ResourceType(vm_resource)][op_name] if ops_map[self.RUN_INSTALLATRON_REPAIR]: # We need to re-initialize installatron after getting ne wkey (Only on VMs) exit_code, outs, errs = runCommand( ['rm', '-fr', '/usr/local/installatron/lib', '/usr/local/installatron/etc/php.ini'], 'rm installatron php.ini') LOG.debug("rm installatron php.ini results: %s: %s -- %s", exit_code, errs, outs) exit_code, outs, errs = runCommand(['curl', '-O', 'https://data.installatron.com/installatron-plugin.sh'], 'curl installatron-plugin') LOG.debug("curl installatron-plugin result: %s: %s -- %s", exit_code, errs, outs) exit_code, outs, errs = runCommand(['chmod', '+x', 'installatron-plugin.sh'], 'chmod plugin') LOG.debug("chmod plugin result: %s: %s -- %s", exit_code, errs, outs) exit_code, outs, errs = runCommand(['./installatron-plugin.sh', '-f', '--quick'], 'rebuild plugin') LOG.debug("rebuild plugin result: %s: %s -- %s", exit_code, errs, outs) def set_mysql_password_cpanel(self, *args: Any) -> Any: """Generates and sets a random mysql password for cPanel""" op_name = 'set_mysql_password_cpanel' password = random_password() exit_code, outs, errs = runCommand( ['/scripts/mysqlpasswd', 'root', password], 'mysql password', omitString=password) if exit_code != 0: return False, self.build_result_dict(outs, errs, op_name) exit_code, outs, errs = runCommand(['/scripts/mysqlconnectioncheck'], 'restart mysql') if exit_code != 0: return False, self.build_result_dict(outs, errs, op_name) def enable_secure_tmp_cpanel(self, *args: Any) -> Any: """Re-secures the /tmp directory""" op_name = 'enable_secure_tmp_cpanel' exit_code, outs, errs = runCommand(['rm', '-f', '/var/cpanel/version/securetmp_disabled'], 're-secure tmp dir') if exit_code != 0: return False, self.build_result_dict(outs, errs, op_name) def cpanel_prep(self, *args: Any) -> Any: """Pre-installs and prepares cPanel on the server""" op_name = 'cpanel_prep' try: self._check_hostname_cpanel() self._yum_update_cpanel() self._download_cpanel() self._exclude_nydus_from_auto_restart_cpanel() self._install_cpanel() self._install_installatron_cpanel() self._config_cpanel() self._disable_secure_tmp_cpanel() except CPanelException as ex: return False, self.build_result_dict(ex.outs, ex.errs, op_name) return self.build_result_dict('cpanel_prep complete', '', op_name) def _check_hostname_cpanel(self) -> bool: """Checks that the server's fully qualified hostname is valid for CPanel `hostname` -> "PT-Test.secureserver.net" but `hostname -f` -> "localhost". /etc/hostname: pt-test.secureserver.net /etc/hosts: 127.0.0.1 localhost PT-Test.secureserver.net # cloud-controlled; do not change 10.192.28.50 pt-test.secureserver.net pt-test CHANGE: removed the PT-Test on the 127.0.0.1 line. `hostname` -> "PT-Test.secureserver.net" `hostname -f` -> "pt-test.secureserver.net" """ hostname = socket.gethostbyaddr(socket.gethostname())[0] if len(hostname.split('.')) < 3: raise CPanelException( '', 'cPanel installation requires a fully-qualified hostname ({} is not enough)'.format(hostname)) return True def _yum_update_cpanel(self) -> bool: """Performs a yum update on the server :raises CPanelException: If the command fails """ exit_code, outs, errs = self._yum_update() if exit_code != 0: raise CPanelException(outs, errs) return True def _download_cpanel(self) -> bool: """Downloads cPanel to the server :raises CPanelException: If the command fails """ exit_code, outs, errs = runCommand( ['wget', 'https://securedownloads.cpanel.net/latest', '-O', '/root/cpinstall'], 'download cpinstall') if exit_code != 0: raise CPanelException(outs, errs) return True def _exclude_nydus_from_auto_restart_cpanel(self) -> bool: # pylint:disable=invalid-name """Exclude Nydus services from cPanel's auto restarts. cPanel restarts services with outdated dependencies during install. It does this very generally, and Nydus services are included and stopped in the middle of install, causing terminal failure. This method adds Nydus services to the exclusion list so they are not restarted. See also, on a cPanel system: - /usr/local/cpanel/scripts/find_outdated_services - /usr/local/cpanel/Cpanel/ProcessCheck/Outdated.pm """ os.makedirs(os.path.dirname(self.AUTO_RESTART_EXCLUSION_FILE), exist_ok=True) content = '%s\n' % '\n'.join(self.AUTO_RESTART_EXCLUDE_SERVICES) create_file(self.AUTO_RESTART_EXCLUSION_FILE, content) return True def _install_cpanel(self) -> bool: """Installs cPanel on the server :raises CPanelException: If the command fails """ exit_code, outs, errs = runCommand(['bash', '/root/cpinstall'], 'install cpanel') if exit_code != 0: raise CPanelException(outs, errs) return True def _install_installatron_cpanel(self) -> bool: """Installs Installatron plugin on the server :raises CPanelException: If the command fails """ exit_code, outs, errs = runCommand( ['rpm', '-U', '-h', '-v', 'http://data.installatron.com/installatron-plugin-cpanel-latest.noarch.rpm'], 'install installatron') if exit_code != 0: raise CPanelException(outs, errs) return True def _config_cpanel(self) -> bool: """Adds initial cPanel configuration :raises CPanelException: If there is a problem running any commands """ try: replace_dict = {'NS2': 'NS2 ns2.secureserver.net\n', 'NS': 'NS ns1.secureserver.net\n', 'ETHDEV': 'ETHDEV eth0\n', 'CONTACTEMAIL': 'CONTACTEMAIL root@cpaneltmp.secureserver.net\n', '^ADDR .*': 'ADDR\n'} replace_file_lines_multiple('/etc/wwwacct.conf', replace_dict) mainipfile = '/var/cpanel/mainip' if os.path.exists(mainipfile): os.unlink(mainipfile) except OSError as ex: raise CPanelException('', str(ex)) from ex return True def _disable_secure_tmp_cpanel(self) -> bool: """Disables noexec on the /tmp directory :raises CPanelException: If the command fails """ exit_code, outs, errs = runCommand(['touch', '/var/cpanel/version/securetmp_disabled'], 'disable noexec /tmp') if exit_code != 0: raise CPanelException(outs, errs) return True def hulk_whitelist_cpanel(self, from_ip_addr: str, *args: Any) -> Tuple[bool, Dict[str, Any]]: """Allow cPanel access from customer IP :param from_ip_addr: The IP address from which the customer will access the cPanel instance """ op_name = 'hulk_whitelist_cpanel' exit_code, outs, errs = runCommand(['/scripts/cphulkdwhitelist', from_ip_addr], 'cphulk whitelist') return exit_code == 0, self.build_result_dict(outs, errs, op_name) def get_hash_cpanel(self, *args: Any) -> Any: """Set cPanel hash""" op_name = 'get_hash_cpanel' path = '/root/.accesshash' if os.path.exists(path) and os.path.getsize(path) == 0: os.unlink(path) if not os.path.exists(path): exit_code, outs, errs = runCommand(['/usr/local/cpanel/whostmgr/bin/whostmgr', 'setrhash'], 'generate hash') if exit_code != 0: return False, self.build_result_dict(outs, errs, op_name) with open(path, 'r', encoding='utf-8') as hashf: cphash = hashf.read() return ''.join(cphash.split('\n')) def get_api_token_cpanel(self, *args: Any) -> Any: """Get cPanel API Token. Revoke any existing nydus-generated tokens before generating a new token.""" op_name = 'get_api_token_cpanel' token_prefix = 'dashboard_generated_' token_suffix = ShortUUID().random(length=SHORT_UUID_DEFAULT_LENGTH) token_name = token_prefix + token_suffix # Retrieve and revoke any existing tokens matching the token prefix exit_code, outs, errs = runCommand(['/usr/local/cpanel/bin/whmapi1', '--output=jsonpretty', 'api_token_list'], 'get api tokens') if exit_code != 0: return False, self.build_result_dict(outs, errs, op_name) tokens_json = json.loads(outs) tokens = tokens_json.get('data').get('tokens') for old_token_name in tokens.keys(): if old_token_name.startswith(token_prefix): # Revoke token runCommand(['/usr/local/cpanel/bin/whmapi1', '--output=jsonpretty', 'api_token_revoke', 'token_name={old_token_name}'.format(old_token_name=old_token_name)], 'revoke token') exit_code, outs, errs = runCommand(['/usr/local/cpanel/bin/whmapi1', '--output=jsonpretty', 'api_token_create', "token_name={token_name}".format(token_name=token_name)], 'generate api token') if exit_code != 0: return False, self.build_result_dict(outs, errs, op_name) token_json = json.loads(outs) token_data = token_json.get('data') if isinstance(token_data, dict): token = token_data.get('token') if token is not None: return self.encrypt(token).decode('utf-8') return False, self.build_result_dict('', 'Invalid token json returned from cpanel', op_name) class CentOSCPanel(CentOS, LinuxCPanel): pass class CentOS6CPanel(CentOS6, CentOSCPanel): # pylint: disable=too-many-ancestors def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called raise NotImplementedError class CentOS7CPanel(CentOS7, CentOSCPanel): # pylint: disable=too-many-ancestors pass class AlmaLinux8CPanel(AlmaLinux8, CentOSCPanel): # pylint: disable=too-many-ancestors pass class AlmaLinux9CPanel(AlmaLinux9, CentOSCPanel): # pylint: disable=too-many-ancestors pass class DebianCPanel(Debian, LinuxCPanel): def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called raise NotImplementedError class Debian8CPanel(Debian8, DebianCPanel): # pylint: disable=too-many-ancestors pass class Ubuntu1604CPanel(Ubuntu1604, DebianCPanel): # pylint: disable=too-many-ancestors pass linux_ispconfig.py000064400000012336147205401360010325 0ustar00# -*- coding: utf-8 -*- import logging import re from typing import Dict, Any from mysql.connector import connection from customer_local_ops import OpType from customer_local_ops.operating_system.linux import Linux, CentOS, CentOS6, CentOS7, Debian, Debian8, Ubuntu1604 from customer_local_ops.control_panel.ispconfig import OSISPConfig LOG = logging.getLogger(__name__) class LinuxISPConfig(Linux, OSISPConfig): """ ISPConfig Customer Local Ops for the Linux OS. All function names should contain 'ispconfig' so as not to override the OS ops """ op_type = OpType.CONTROL_PANEL_OPERATING_SYSTEM os_op_instance = None def change_ispconfig_hostname(self, payload: Dict[str, Any]) -> None: """Changes the server hostname for ispconfig :param payload: A dict containing input data """ new_hostname = payload['hostname'] self._change_hostname_in_db(new_hostname) def _change_hostname_in_db(self, new_hostname: str) -> None: """Changes the server hostname for ispconfig in the database :param new_hostname: The new hostname string """ cnx = self._get_database_connection() cursor = cnx.cursor() try: cursor.execute('select config from server where server_id = 1') config = cursor.fetchone()[0] config = self._replace_hostname_in_config(config, new_hostname) cursor.execute('UPDATE server SET config = %s WHERE server_id = 1', (config, )) cnx.commit() finally: cursor.close() cnx.close() def _get_database_connection(self) -> connection.MySQLConnection: """Returns an ispconfig MySQL database connection""" sql_username = self._get_sql_username() sql_password = self._get_sql_password() cnx = connection.MySQLConnection(user=sql_username, password=sql_password, host='127.0.0.1', database='dbispconfig') return cnx def _replace_hostname_in_config(self, config: str, new_hostname: str) -> str: """Replaces hostname in ispconfig configuration and returns the new configuration string :param config: Configuration string in which to replace the hostname :param new_hostname: The new hostname string """ pattern = r'hostname=(?P[\w\-.]*)' match = re.search(pattern, config) old_hostname = match.groupdict()['hostname'] config = config.replace(old_hostname, new_hostname) return config def change_ispconfig_password(self, payload: Dict[str, Any], op_name: str) -> None: """Changes the user password for ispconfig :param payload: A dict containing input data :param op_name: The name of the op """ new_password = self._get_new_password(payload) self._change_password_in_db(new_password) def _change_password_in_db(self, new_password: str) -> None: """Changes the server password for ispconfig in the database :param new_password: The new password string """ cnx = self._get_database_connection() cursor = cnx.cursor() try: cursor.execute('UPDATE sys_user SET passwort = md5(%s) WHERE username = %s', (new_password, 'admin')) cnx.commit() finally: cursor.close() cnx.close() def _get_new_password(self, payload: Dict[str, Any]) -> str: """Decrypts the new password from an encrypted string :param payload: A dict containing input data """ encrypted_password = payload['encrypted_password'] new_password = self.decrypt(encrypted_password) return new_password def _get_sql_password(self) -> str: """Returns the MySQL database password from configuration""" return self._get_config_value('db_password') def _get_sql_username(self) -> str: """Returns the MySQL database user from configuration""" return self._get_config_value('db_user') def _get_config_value(self, key: str) -> str: """Retrieves a value from configuration from its key :param key: The configuration key for the value """ with open('/usr/local/ispconfig/interface/lib/config.inc.php', encoding='utf-8') as f: ispConfig = f.read() pattern = re.escape(r"$conf['{0}']".format(key)) + \ r'\s*=\s*[\'"](?P[^"\']*)["\']' match = re.search(pattern, ispConfig) value = match.groupdict()['value'] return value class CentOSISPConfig(CentOS, LinuxISPConfig): def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called raise NotImplementedError class CentOS6ISPConfig(CentOS6, CentOSISPConfig): # pylint: disable=too-many-ancestors pass class CentOS7ISPConfig(CentOS7, CentOSISPConfig): # pylint: disable=too-many-ancestors pass class DebianISPConfig(Debian, LinuxISPConfig): pass class Debian8ISPConfig(Debian8, DebianISPConfig): # pylint: disable=too-many-ancestors def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called raise NotImplementedError class Ubuntu1604ISPConfig(Ubuntu1604, DebianISPConfig): # pylint: disable=too-many-ancestors pass linux_plesk.py000064400000055752147205401360007473 0ustar00# -*- coding: utf-8 -*- from typing import Dict, Any import json import logging import os import re import shlex import sys from enum import IntEnum from fileinput import FileInput from customer_local_ops import OpType, ResourceType, NydusResult from customer_local_ops.operating_system.linux import (AlmaLinux8, AlmaLinux9, Linux, CentOS, CentOS6, CentOS7, Debian, Debian8, Debian10, Debian11, Debian12, Ubuntu1604, Ubuntu2004, Ubuntu2204, Ubuntu2404) from customer_local_ops.control_panel.plesk import OSPlesk from customer_local_ops.util.execute import runCommand, run_uapi_command, run_multiple_uapi_commands from customer_local_ops.util.retry import Retry LOG = logging.getLogger(__name__) PLESK_DIR_1 = "/opt/psa" PLESK_DIR_2 = "/usr/local/psa" class LinuxPlesk(Linux, OSPlesk): """ Plesk Customer Local Ops for the Linux OS. All function names should contain 'plesk' so as not to override the OS ops """ op_type = OpType.CONTROL_PANEL_OPERATING_SYSTEM PLESK_INSTALL_LOCATIONS = [ PLESK_DIR_1, PLESK_DIR_2, ] plesk_dir = None RETRYABLE_ERRS = ['The Plesk administrator password cannot be changed until the server cloning is finished', 'No connection could be made because the target machine actively refused it', 'Could not resolve host'] def get_plesk_dir(self) -> str: """Find the installation directory for plesk :return: full path to plesk installation """ # Look for plesk in any of several locations. On Ubuntu, it's currently installed in # /opt/psa and on CentOS, it can be found in /usr/local/psa. The list below could # easily be made into a configurable item so that new potential locations could be # added without having to change the code. if self.plesk_dir is not None: return self.plesk_dir for loc in self.PLESK_INSTALL_LOCATIONS: if os.path.isdir(loc): LOG.info(loc) self.plesk_dir = loc return self.plesk_dir raise RuntimeError("plesk path could not be found") def license_plesk(self, activation_key: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> NydusResult: """Run license utility on local vm with activation key :param activation_key: Key value passed back from license plesk op on hfs executor :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data """ LOG.info("install plesk license on '%s'", str(self)) op_name = 'license_plesk' command = 'license' full_command = self.get_path_plesk(command) LOG.info("full command: %s", full_command) exit_code, outs, errs = run_uapi_command([full_command, '--install', activation_key], 'set Plesk License', 'license_plesk') return self._result_handler(exit_code, outs, errs, op_name, intermediate_result=intermediate_result) # pylint: disable=too-many-locals def enable_plesk(self, vm_ip: str, vm_resource: str, plesk_user: str, plesk_pass: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> NydusResult: """Enable plesk on local vm :param vm_ip: External IP address of the VM :param vm_resource: The resource name for the third-party hosting provider :param plesk_user: User name to be used on Plesk instance :param plesk_pass: Password to be used on Plesk instance :param intermediate_result: Dict containing metadata for retries :raises DecryptError: if there is a problem with decrypting the password :return: tuple with success, error data """ LOG.info("enable plesk") op_name = self.OP_ENABLE_PLESK password_arg = shlex.quote(self.decrypt(plesk_pass)) ops_map = self.PLESK_OPS_RESOURCE_ATTRIBUTE_MAP[ResourceType(vm_resource)][op_name] init_conf_cmd = self.get_path_plesk('init_conf') check_configured_cmd = '{} --check-configured'.format(init_conf_cmd) exit_code, outs, errs = run_uapi_command(check_configured_cmd, "Check Plesk configured", op_name, use_shell=True) if exit_code != 0: # Plesk is not configured init_conf_setup_flag = '--init' else: # Plesk is configured init_conf_setup_flag = '--update' prep_cmds = [ ('set minimum password strength', '{server_pref} -u -min_password_strength medium'.format( server_pref=self.get_path_plesk('server_pref'))), ('setup Plesk', ops_map[self.SETUP_CMD].format( init_conf_cmd=init_conf_cmd, # init_conf_setup_flag=ops_map[self.INIT_CONF_SETUP_FLAG], init_conf_setup_flag=init_conf_setup_flag, plesk_user=plesk_user, password=password_arg)), ('check config', check_configured_cmd), ('set Poweruser', '{set_poweruser_cmd} --on'.format(set_poweruser_cmd=self.get_path_plesk('poweruser'))), ('set auto updates', 'plesk db "INSERT INTO misc(param, val) ' + 'VALUES(\'automaticSystemPackageUpdates\', \'true\') ON DUPLICATE KEY UPDATE val = \'true\';"') ] if ops_map[self.RUN_HIDE_INTERNAL_IP]: prep_cmds += [ ('hide internal ip', '''sed -i'' 's/blacklist=".*"/blacklist="{vm_ip}"/' ''' '/usr/local/psa/admin/conf/panel.ini'.format(vm_ip=vm_ip)), ('reread ips', 'plesk bin ipmanage --reread') ] if ops_map[self.RUN_DISABLE_SESSION_IP_CHECK]: prep_cmds += [ ('disable session ip check', 'plesk db "' 'INSERT INTO misc(param,val) ' 'VALUES(\'disable_check_session_ip\', \'true\') ' 'ON DUPLICATE KEY UPDATE val = \'true\';"') ] exit_code, outs, errs = run_multiple_uapi_commands(prep_cmds, op_name, use_shell=True, omit_string=password_arg) return self._result_handler(exit_code, outs, errs, op_name, intermediate_result=intermediate_result) def site_list_plesk(self, *args: Any, intermediate_result: Dict[str, Any] = None) -> Any: """Retrieve a list of Plesk sites :param intermediate_result: Dict containing metadata for retries :return: list of sites or tuple with error data """ LOG.info("get site list") site_list = {} exit_code, outs, errs = runCommand(self.get_path_plesk('subscription') + " --list", "site_list_plesk: get_subscriptions", useShell=True) if exit_code != 0: return self._result_handler(exit_code, outs, errs, 'site_list_plesk: get_subscriptions', intermediate_result) site_list['subscriptions'] = outs.split('\n') site_cmd = self.get_path_plesk('site') exit_code, outs, errs = runCommand(site_cmd + " --list", "site_list_plesk: get_sites", useShell=True) if exit_code != 0: return self._result_handler(exit_code, outs, errs, 'site_list_plesk: get_sites', intermediate_result) site_list['sites'] = outs.split('\n') required_fields = {'FTP Login': 'ftp_login', 'IP address': 'ip_address', 'Disk space used by httpdocs': 'diskused', 'Hosting type': 'webspace'} for i, site in enumerate(site_list['sites']): site_data = {'name': site} exit_code, outs, errs = runCommand(site_cmd + " --info " + site, "site_list_plesk: get_site_info", useShell=True) if exit_code != 0: return self._result_handler(exit_code, outs, errs, 'site_list_plesk: get_site_info', intermediate_result) output = outs.split('\n') for line in output: data = [x.strip() for x in line.split(':')] logging.info("data: %s", data) if len(data) == 2: key = data[0] if key in required_fields: site_data[required_fields[key]] = data[1] site_list['sites'][i] = site_data logging.info(json.dumps(site_list)) return site_list def server_prep_plesk(self, *args: Any, intermediate_result: Dict[str, Any] = None) -> NydusResult: """ Install Plesk on a server :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data """ LOG.info("install plesk on '%s'", str(self)) op_name = 'server_prep_plesk' # download plesk-installer. keep: provides compatability for updates, troubleshooting, etc. prepCmds = [ ('Download Plesk', 'wget https://autoinstall.plesk.com/plesk-installer'), ('Modify Perms', 'chmod +x plesk-installer'), ('Install Plesk', 'sh plesk-installer --select-product-id=plesk --installation-type Typical ' + '--select-release-latest --notify-email admin@example.com'), ('Delete Docker Interface', 'ip link del docker0') ] for purpose, cmd in prepCmds: exit_code, outs, errs = run_uapi_command(cmd, purpose, op_name, use_shell=True) if exit_code != 0: return self._result_handler(exit_code, outs, errs, op_name, intermediate_result=intermediate_result) return self._result_handler(exit_code, outs, errs, op_name, intermediate_result=intermediate_result) def get_client_plesk(self, *args: Any, intermediate_result: Dict[str, Any] = None) -> Any: """ Get Plesk SSO URL :param intermediate_result: Dict containing metadata for retries :return: SSO URL string or tuple with error data """ exit_code, outs, errs = runCommand(self.get_path_plesk('admin') + " --get-login-link", "get sso link", useShell=True) if exit_code != 0: return self._result_handler(exit_code, outs, errs, 'get_client_plesk', intermediate_result=intermediate_result) links = outs.split('\n') sso_url = str(links[0]) return self.encrypt(sso_url).decode('utf-8') def change_hostname_plesk(self, hostname: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> NydusResult: """ Change hostname on Plesk :param hostname: The new server hostname :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data """ op_name = 'change_hostname_plesk' rcmd = self.get_path_plesk('server_pref') + ' --update' + ' -hostname ' + hostname exit_code, outs, errs = run_uapi_command(rcmd, 'set Plesk hostname', op_name, use_shell=True) return self._result_handler(exit_code, outs, errs, op_name, intermediate_result=intermediate_result) def change_admin_password_plesk(self, plesk_admin_pass: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> NydusResult: """ Change admin password on Plesk :param plesk_admin_pass: Encrypted password for Plesk admin user :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data """ op_name = 'change_admin_password_plesk' password = self.decrypt(plesk_admin_pass) # Perform operations on Control Panel plesk_bin = self.get_path_plesk('init_conf') command = [plesk_bin, '--set-admin-password', '-passwd', password] exit_code, outs, errs = run_uapi_command( command, 'set Plesk admin password', op_name, omit_string=password) return self._result_handler(exit_code, outs, errs, op_name, intermediate_result=intermediate_result) def configure_postfix(self, relay_address: str, op_name: str, intermediate_result: Dict[str, Any] = None) -> NydusResult: """Configure the postfix MTA. :param relay_address: IP address or host name of the mail relay to set :param op_name: name of the operation being executed :param intermediate_result: Dict containing metadata for retries """ if relay_address is None: return class SearchState(IntEnum): SEARCHING = 0 SKIPPING = 1 DONE = 2 # In the configuration file distributed with postfix, there are a either a number of commented-out relayhost # lines or a blank relay host line. Add our new line immediately after the last commented-out relayhost line # or replace the blank relayhost line try: relayhost_cmt = re.compile(r'^[ \t]*#[ \t]*relayhost[ \t]*=') relayhost_blnk = re.compile(r'^[ \t]*relayhost[ \t]*=') relayhost_line = "relayhost = [{}]\n".format(relay_address) state = SearchState.SEARCHING with FileInput('/etc/postfix/main.cf', inplace=True) as stream: for line in stream: if relayhost_blnk.match(line): sys.stdout.write(relayhost_line) state = SearchState.DONE continue if relayhost_cmt.match(line): if state == SearchState.SEARCHING: state = SearchState.SKIPPING else: if state == SearchState.SKIPPING: # We've found the first line after the other commented-out relayhost lines # Add the new relayhost line here and set the state to indicate that we've # finished. sys.stdout.write(relayhost_line) state = SearchState.DONE sys.stdout.write(line) # If we get here and the state isn't DONE, then just add the line to the end of the file if state != SearchState.DONE: with open('/etc/postfix/main.cf', 'a', encoding='utf-8') as f: f.write(relayhost_line) except Exception as ex: # pylint: disable=broad-except LOG.error("cp_os_op %s.configure_postfix result(fail): %s", op_name, ex) return self._result_handler(1, '', str(ex), op_name, intermediate_result=intermediate_result) LOG.info("restarting postfix") my_op_name = op_name + ': restart postfix' return self.run_command_and_handle_result(['systemctl', 'restart', 'postfix'], my_op_name, intermediate_result=intermediate_result) def set_outgoing_email_ip(self, address: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> NydusResult: """Set Plesk's outgoing e-mail IP address. This only works with Postfix mail server. See General #6 for more information: https://docs.plesk.com/en-US/obsidian/administrator-guide/mail/configuring-serverwide-mail-settings.59430/ :param address: IP address from which to send e-mail :param intermediate_result: Dict containing result data and meta data for retries :return: Nydus operation result """ return self.run_command_and_handle_result( [self.get_path_plesk(filename='mailserver'), '--set-outgoing-email-mode', 'explicit-ip', '-explicit-ipv4', address], 'set_outgoing_email_ip', intermediate_result=intermediate_result) class CentOSPlesk(CentOS, LinuxPlesk): PLESK_INSTALL_LOCATIONS = [ PLESK_DIR_2 ] def configure_mta(self, payload, unused=None, intermediate_result=None) -> NydusResult: """Configure mail transfer agent on a CentOS-based Plesk server :param payload: Dict containing op params :param intermediate_result: Dict containing result data and meta data for retries :return: tuple with success, retry or error data """ LOG.info("%s.configure_mta", self) return self.do_configure_mta(payload, 'CentOSPlesk.configure_mta', intermediate_result=intermediate_result) def do_configure_mta(self, payload: Dict[str, Any], op_name: str, intermediate_result: Dict[str, Any] = None) -> NydusResult: """Configure mail transfer agent on a CentOS-based Plesk server :param payload: Dict containing op params :param op_name: The name of the op, including the classname :param intermediate_result: Dict containing result data and meta data for retries :return: tuple with success, retry or error data """ LOG.info("%s.do_configure_mta", self) result = self.install_postfix(op_name, intermediate_result=intermediate_result) if isinstance(result, Retry) or not result[0]: return result relay_address = payload.get('relay_address') LOG.info("%s %s start relay_address: %s", self.get_op_type().value, op_name, relay_address) return self.configure_postfix(relay_address, op_name, intermediate_result=intermediate_result) def install_postfix(self, op_name: str, intermediate_result: Dict[str, Any] = None) -> NydusResult: """Install postfix on a CentOS-based Plesk server :param op_name: The name of the op, including the classname :param intermediate_result: Dict containing result data and meta data for retries :return: tuple with success, retry or error data """ LOG.info("%s.install_postfix", self) my_op_name = op_name + ': yum_clean_all' exit_code, outs, errs = self._run_yum_command(['yum', 'clean', 'all'], my_op_name) if exit_code != 0: return self._result_handler(exit_code, outs, errs, my_op_name, intermediate_result=intermediate_result) my_op_name = op_name + ': remove_sendmail' exit_code, outs, errs = self._run_yum_command(['yum', '-y', 'remove', 'sendmail'], my_op_name) if exit_code != 0: return self._result_handler(exit_code, outs, errs, my_op_name, intermediate_result=intermediate_result) my_op_name = op_name + ': install postfix' exit_code, outs, errs = self._run_yum_command(['yum', '-y', 'install', 'postfix'], my_op_name) return self._result_handler(exit_code, outs, errs, my_op_name, intermediate_result=intermediate_result) class CentOS6Plesk(CentOS6, CentOSPlesk): # pylint: disable=too-many-ancestors def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called raise NotImplementedError class CentOS7Plesk(CentOS7, CentOSPlesk): # pylint: disable=too-many-ancestors pass class AlmaLinux8Plesk(AlmaLinux8, CentOSPlesk): # pylint: disable=too-many-ancestors pass class AlmaLinux9Plesk(AlmaLinux9, CentOSPlesk): # pylint: disable=too-many-ancestors pass class DebianPlesk(Debian, LinuxPlesk): def configure_mta(self, payload, unused=None, intermediate_result=None) -> NydusResult: """Configure mail transfer agent on a Debian-based Plesk server :param payload: Dict containing op params :param intermediate_result: Dict containing result data and meta data for retries :return: tuple with success, retry or error data """ LOG.info("DebianPlesk.configure_mta") return self.do_configure_mta(payload, 'DebianPlesk.configure_mta', intermediate_result=intermediate_result) def do_configure_mta(self, payload: Dict[str, Any], op_name: str, intermediate_result: Dict[str, Any] = None) -> NydusResult: """Configure mail transfer agent on a Debian-based Plesk server :param payload: Dict containing op params :param op_name: The name of the op, including the classname :param intermediate_result: Dict containing result data and meta data for retries :return: tuple with success, retry or error data """ LOG.info("DebianPlesk.do_configure_mta") result = self.install_postfix(op_name, intermediate_result=intermediate_result) if isinstance(result, Retry) or not result[0]: return result relay_address = payload.get('relay_address') LOG.info("%s %s start relay_address: %s", self.get_op_type().value, op_name, relay_address) return self.configure_postfix(relay_address, op_name, intermediate_result=intermediate_result) def install_postfix(self, op_name: str, intermediate_result: Dict[str, Any] = None) -> NydusResult: """Install postfix on a CentOS-based Plesk server :param op_name: The name of the op, including the classname :param intermediate_result: Dict containing result data and meta data for retries :return: tuple with success, retry or error data """ LOG.info("DebianPlesk.install_postfix") my_op_name = op_name + ': remove_sendmail' exit_code, outs, errs = runCommand(['apt-get', 'remove', '-y', 'sendmail'], my_op_name) if exit_code != 0: return self._result_handler(exit_code, outs, errs, my_op_name, intermediate_result=intermediate_result) result = self._install('postfix') if isinstance(result, Retry): return result my_op_name = op_name + ': install postfix' exit_code, outs, errs = result return self._result_handler(exit_code, outs, errs, my_op_name, intermediate_result=intermediate_result) class Debian8Plesk(Debian8, DebianPlesk): # pylint: disable=too-many-ancestors def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called raise NotImplementedError class Debian10Plesk(Debian10, DebianPlesk): # pylint: disable=too-many-ancestors def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called raise NotImplementedError class Debian11Plesk(Debian11, DebianPlesk): # pylint: disable=too-many-ancestors def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called raise NotImplementedError class Debian12Plesk(Debian12, DebianPlesk): # pylint: disable=too-many-ancestors def __init__(self, *args, **kwargs): # pylint: disable=super-init-not-called raise NotImplementedError class Ubuntu1604Plesk(Ubuntu1604, DebianPlesk): # pylint: disable=too-many-ancestors PLESK_INSTALL_LOCATIONS = [ PLESK_DIR_1 ] class Ubuntu2004Plesk(Ubuntu2004, DebianPlesk): # pylint: disable=too-many-ancestors PLESK_INSTALL_LOCATIONS = [ PLESK_DIR_1 ] class Ubuntu2204Plesk(Ubuntu2204, DebianPlesk): # pylint: disable=too-many-ancestors PLESK_INSTALL_LOCATIONS = [ PLESK_DIR_1 ] class Ubuntu2404Plesk(Ubuntu2404, DebianPlesk): # pylint: disable=too-many-ancestors PLESK_INSTALL_LOCATIONS = [ PLESK_DIR_1 ] plesk.py000064400000032557147205401360006252 0ustar00# -*- coding: utf-8 -*- import logging import os from typing import Any, Tuple, Dict, List from customer_local_ops import NydusResult, Ops, ResourceType, OpType from customer_local_ops.util.retry import retry, Retry LOG = logging.getLogger(__name__) # These two constants are used to define the interval and timeout for retrying a request when the Python/PHP # DLL conflict is detected on Windows platforms. We want to retry the request twice, for a total of three attempts, # including the original attempt. The timeout value is technically a little larger than it needs to be to allow for # any slop in the timing. CHECK_FOR_DLL_CONFLICT_RETRY_INTERVAL = 10 CHECK_FOR_DLL_CONFLICT_RETRY_TIMEOUT = 25 class OSPlesk: """Mixin class representing common functionality and constants for Plesk between all OSes""" OP_ENABLE_PLESK = 'enable_plesk' RUN_DISABLE_SESSION_IP_CHECK = 'run_disable_session_ip_check' RUN_HIDE_INTERNAL_IP = 'run_hide_internal_ip' SETUP_CMD = 'setup_cmd' PLESK_SETUP_CMD_FORMAT_STRING_LIST = ['{init_conf_cmd} {init_conf_setup_flag}', '-name admin', '-passwd {password}', '-license_agreed true', '-email noreply@secureserver.net', '-admin_info_not_required true'] PLESK_OPS_RESOURCE_ATTRIBUTE_MAP = { ResourceType.OPENSTACK: { OP_ENABLE_PLESK: { RUN_DISABLE_SESSION_IP_CHECK: False, RUN_HIDE_INTERNAL_IP: True, SETUP_CMD: ' '.join(PLESK_SETUP_CMD_FORMAT_STRING_LIST) }, }, ResourceType.OVH: { OP_ENABLE_PLESK: { RUN_DISABLE_SESSION_IP_CHECK: True, RUN_HIDE_INTERNAL_IP: False, SETUP_CMD: ' '.join(PLESK_SETUP_CMD_FORMAT_STRING_LIST) }, }, ResourceType.VIRTUOZZO_VM: { OP_ENABLE_PLESK: { RUN_DISABLE_SESSION_IP_CHECK: True, RUN_HIDE_INTERNAL_IP: False, SETUP_CMD: ' '.join(PLESK_SETUP_CMD_FORMAT_STRING_LIST) }, }, ResourceType.OPENSTACK_HOSTINGCLOUD: { OP_ENABLE_PLESK: { RUN_DISABLE_SESSION_IP_CHECK: True, RUN_HIDE_INTERNAL_IP: False, SETUP_CMD: ' '.join(PLESK_SETUP_CMD_FORMAT_STRING_LIST) }, } } def get_plesk_dir(self) -> str: """Find the installation directory for Plesk :raises RuntimeError: If a Plesk path cannot be found :return: Full path to Plesk installation """ # Implement this in OSPlesk child classes (e.g. WindowsPlesk, LinuxPlesk) raise NotImplementedError def get_path_plesk(self, filename: str = None, dirs: List[str] = None) -> str: """Get the path for a Plesk file/command/directory :param filename: Name of file or executable. If None, only directory is returned. :param dirs: List of directories under Plesk directory, in order of depth. These are joined to make the path. :raises RuntimeError: If a Plesk path cannot be found :return: Full path to Plesk file/command/directory """ if dirs is None: dirs = ['bin'] args = dirs if filename is not None: args.append(filename) return os.path.join(self.get_plesk_dir(), *args) class Plesk(Ops): op_type = OpType.CONTROL_PANEL RETRY_LICENSE_PLESK_TIMEOUT = 300 RETRY_LICENSE_PLESK_INTERVAL = 20 @retry(RETRY_LICENSE_PLESK_INTERVAL, RETRY_LICENSE_PLESK_TIMEOUT) def license_plesk(self, os_op: str, activation_key: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> Tuple[bool, Dict[str, Any]]: """Run license utility on local vm with activation key :param os_op: Operating system customer_local_ops class of target server :param activation_key: Key value passed back from license plesk op on hfs executor :param intermediate_result: Dict containing retry-related metadata :return: tuple with success, error data """ LOG.info("license plesk") op_name = 'license_plesk' cp_os_op_instance = self.get_os_op(os_op) os_result = cp_os_op_instance.license_plesk(activation_key, intermediate_result=intermediate_result) return self.build_result_from_other_result(os_result, op_name) RETRY_ENABLE_PLESK_TIMEOUT = 1800 RETRY_ENABLE_PLESK_INTERVAL = 20 @retry(RETRY_ENABLE_PLESK_INTERVAL, RETRY_ENABLE_PLESK_TIMEOUT) def enable_plesk(self, os_op: str, vm_ip: str, vm_resource: str, plesk_user: str, plesk_pass: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> Tuple[bool, Dict[str, Any]]: """Enable plesk on local server :param os_op: Operating system customer_local_ops class of target server :param vm_ip: External IP address of the server :param vm_resource: The resource name for the third-party hosting provider :param plesk_user: User name to be used on Plesk instance :param plesk_pass: Password to be used on Plesk instance :param intermediate_result: Dict containing retry-related metadata :return: tuple with success, error data """ LOG.info("enable plesk") op_name = 'enable_plesk' cp_os_op_instance = self.get_os_op(os_op) os_result = cp_os_op_instance.enable_plesk(vm_ip, vm_resource, plesk_user, plesk_pass, intermediate_result=intermediate_result) return self.build_result_from_other_result(os_result, op_name) RETRY_SITE_LIST_TIMEOUT = 300 RETRY_SITE_LIST_INTERVAL = 20 @retry(RETRY_SITE_LIST_INTERVAL, RETRY_SITE_LIST_TIMEOUT) def site_list(self, os_op: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> Any: """Retrieve a list of Plesk sites :param os_op: Operating system customer_local_ops class of target server :param intermediate_result: Dict containing retry-related metadata :return: list of sites or tuple with error data """ LOG.info("get site list") cp_os_op_instance = self.get_os_op(os_op) return cp_os_op_instance.site_list_plesk(intermediate_result=intermediate_result) RETRY_SERVER_PREP_TIMEOUT = 3600 RETRY_SERVER_PREP_INTERVAL = 20 @retry(RETRY_SERVER_PREP_INTERVAL, RETRY_SERVER_PREP_TIMEOUT) def server_prep(self, os_op: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> Tuple[bool, Dict[str, Any]]: """ Install Plesk on a server :param os_op: Operating system customer_local_ops class of target server :param intermediate_result: Dict containing retry-related metadata :return: tuple with success, error data """ LOG.info("install plesk on '%s'", str(self)) LOG.info("server prep") op_name = 'server_prep' cp_os_op_instance = self.get_os_op(os_op) os_result = cp_os_op_instance.server_prep_plesk(intermediate_result=intermediate_result) return self.build_result_from_other_result(os_result, op_name) RETRY_GET_CLIENT_TIMEOUT = 300 RETRY_GET_CLIENT_INTERVAL = 20 @retry(RETRY_GET_CLIENT_INTERVAL, RETRY_GET_CLIENT_TIMEOUT) def get_client(self, os_op: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> Any: """Get Plesk SSO URL :param os_op: Operating system customer_local_ops class of target server :param intermediate_result: Dict containing retry-related metadata :return: SSO URL string or tuple with error data """ LOG.info("get client") cp_os_op_instance = self.get_os_op(os_op) return cp_os_op_instance.get_client_plesk(intermediate_result=intermediate_result) RETRY_CONFIGURE_MTA_TIMEOUT = 1200 # seconds # pylint: disable=invalid-name RETRY_CONFIGURE_MTA_RETRY_INTERVAL = 30 # seconds # pylint: disable=invalid-name # the default steps already do what we want for plesk. stub put here in case we want to change that in the future. @retry(interval=RETRY_CONFIGURE_MTA_RETRY_INTERVAL, timeout=RETRY_CONFIGURE_MTA_TIMEOUT) def configure_mta(self, payload: Dict[str, Any], *args: Any, intermediate_result: Any = None) -> Tuple[bool, Dict[str, Any]]: """Configure mail transfer agent on the Plesk server :param payload: Dict containing op params :param intermediate_result: Dict containing result data and meta data for retries :return: tuple with success, retry or error data """ os_op = payload["os_op"] op_name = 'configure_mta' LOG.info("Plesk.configure_mta start") LOG.info("Plesk.configure_mta deferring to OS operation: %s.%s", str(os_op), op_name) # Tell the OS Op do something now... # Assuming os_op has format module.os e.g. windows.Windows2016: try: cp_os_op_instance = self.get_os_op(os_op) except AttributeError as ex: return False, self.build_result_dict('', str(ex), op_name) os_result = cp_os_op_instance.do_configure_mta(payload, op_name, intermediate_result=intermediate_result) # OS Op could return a retry if isinstance(os_result, Retry): return os_result data = self.get_result_data(os_result) LOG.info("Plesk.configure_mta cp_os_op result - %s - %s - %s", data.success, data.outs, data.errs) return self.build_result_from_other_result(os_result, op_name) def change_password(self, payload: Dict[str, Any], *args: Any) -> Tuple[bool, Dict[str, Any]]: """ Change password on the Plesk server :param payload: Dict containing op params :return: tuple with success or error data """ op_name = 'change_password' os_op = payload["os_op"] LOG.info("Plesk.change_password is a NOOP, deferring to OS operation: %s.%s", str(os_op), op_name) os_op_instance = self.get_os_op(os_op) return os_op_instance.change_password(payload) RETRY_CHANGE_ADMIN_PASSWORD_TIMEOUT = 300 RETRY_CHANGE_ADMIN_PASSWORD_INTERVAL = 20 @retry(RETRY_CHANGE_ADMIN_PASSWORD_INTERVAL, RETRY_CHANGE_ADMIN_PASSWORD_TIMEOUT) def change_admin_password(self, payload: Dict[str, Any], *args: Any, intermediate_result: Dict[str, Any] = None) -> Tuple[bool, Dict[str, Any]]: """ Change admin password on the Plesk server :param payload: Dict containing op params :param intermediate_result: Dict containing retry-related metadata :return: tuple with success or error data """ os_op = payload["os_op"] op_name = 'change_admin_password' cp_os_op_instance = self.get_os_op(os_op) os_result = cp_os_op_instance.change_admin_password_plesk(payload['plesk_admin_pass'], intermediate_result=intermediate_result) return self.build_result_from_other_result(os_result, op_name) RETRY_CHANGE_HOSTNAME_TIMEOUT = 120 RETRY_CHANGE_HOSTNAME_INTERVAL = 20 @retry(RETRY_CHANGE_HOSTNAME_INTERVAL, RETRY_CHANGE_HOSTNAME_TIMEOUT) def change_hostname(self, payload: Dict[str, Any], *args: Any, intermediate_result: Dict[str, Any] = None) -> Tuple[bool, Dict[str, Any]]: """ Change hostname on the Plesk server :param payload: Dict containing op params :param intermediate_result: Dict containing retry-related metadata :return: tuple with success or error data """ # Do some plesk specific work? LOG.debug("Plesk.change_hostname start") os_op = payload["os_op"] op_name = 'change_hostname' hostname = payload['hostname'] cp_os_op_instance = self.get_os_op(os_op) # Tell the OS Op do something now... LOG.info("Plesk.change_hostname deferring to OS operation: %s.%s", str(os_op), op_name) os_result = cp_os_op_instance.change_hostname(payload) data = self.get_result_data(os_result) LOG.info("Plesk.change_hostname os_op_result - %s - %s - %s", data.success, data.outs, data.errs) if not data.success: return self.build_result_from_other_result(os_result, op_name) # Now change hostname for Plesk result = cp_os_op_instance.change_hostname_plesk(hostname, intermediate_result=intermediate_result) return self.build_result_from_other_result(result, op_name) RETRY_SET_OUTGOING_MAIL_TIMEOUT = 300 RETRY_SET_OUTGOING_MAIL_INTERVAL = 20 @retry(RETRY_SET_OUTGOING_MAIL_INTERVAL, RETRY_SET_OUTGOING_MAIL_TIMEOUT) def set_outgoing_email_ip(self, os_op: str, address: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> NydusResult: """Set Plesk's outgoing e-mail IP address. See General #6 for more information: https://docs.plesk.com/en-US/obsidian/administrator-guide/mail/configuring-serverwide-mail-settings.59430/ :param os_op: Operating system customer_local_ops class of target server :param address: IP address from which to send e-mail :param intermediate_result: Dict containing retry-related metadata :return: Nydus operation result """ return self.get_os_op(os_op).set_outgoing_email_ip(address, intermediate_result=intermediate_result) windows_plesk.py000064400000033257147205401360010022 0ustar00# -*- coding: utf-8 -*- from subprocess import list2cmdline from typing import Any, Dict, Tuple, Union, List import logging import glob import json import os from customer_local_ops import OpType, ResourceType, NydusResult from customer_local_ops.operating_system.windows import Windows, Windows2016, Windows2019, Windows2022 from customer_local_ops.control_panel import SCRIPT_BASEPATH from customer_local_ops.control_panel.plesk import OSPlesk from customer_local_ops.util.execute import (runCommand, run_powershell_file, run_uapi_command, run_multiple_uapi_commands, start_powershell_file) from customer_local_ops.util.retry import Retry LOG = logging.getLogger(__name__) PLESK_DIR_17 = 'C:\\Program Files (x86)\\Plesk\\' PLESK_DIR_12 = 'C:\\Program Files (x86)\\Parallels\\Plesk\\' # Number of seconds for Nydus to wait before retrying the previous workflow stop if a DLL conflict was detected DLL_CONFLICT_COMMAND_RETRY_INTERVAL = 10 PLESK_FIX_DLL_CONFLICT_SCRIPT_LOG_FILE = r'C:\Windows\TEMP\plesk-fix-dll-conflict.log' CmdsType = Union[str, List[str], List[Tuple[str, Union[str, List[str]]]]] def has_dll_version_conflict(text: str) -> bool: """ Check to see of the specified text contains an indication of the Python/PHP DLL conflict :param text: The text to be checked :return: True if the conflict is detected; otherwise, False """ # 'vcruntime140.dll' 14.0 is not compatible with this PHP build linked with 14.16 in Unknown on line 0 conflict_detected = "'vcruntime140.dll' 14.0 is not compatible" in text if conflict_detected: LOG.debug("DLL conflict detected") else: LOG.debug("No DLL conflict detected") return conflict_detected class WindowsPlesk(Windows, OSPlesk): """ Plesk Customer Local Ops for the Windows OS. All function names should contain 'plesk' so as not to override the OS ops """ op_type = OpType.CONTROL_PANEL_OPERATING_SYSTEM plesk_dir = None RETRYABLE_ERRS = ['The Plesk administrator password cannot be changed until the server cloning is finished', 'No connection could be made because the target machine actively refused it', 'Could not resolve host'] def get_plesk_dir(self) -> str: """Find the installation directory for plesk :raises RuntimeError: If a plesk path cannot be found :return: full path to plesk installation """ if self.plesk_dir is None: # environment variable for plesk dir. if os.getenv('plesk_dir'): self.plesk_dir = os.getenv('plesk_dir') # plesk 17 default elif os.path.exists(PLESK_DIR_17): self.plesk_dir = PLESK_DIR_17 # plesk 12 default elif os.path.exists(PLESK_DIR_12): self.plesk_dir = PLESK_DIR_12 # go find a plesk! else: pleskpath = glob.glob('C:\\Program Files (x86)\\**\\Plesk\\', recursive=True) if pleskpath and isinstance(pleskpath, list): self.plesk_dir = pleskpath[0] if self.plesk_dir is not None: LOG.info("get_plesk_dir: %s", self.plesk_dir) return self.plesk_dir raise RuntimeError("plesk path could not be found") def license_plesk(self, activation_key: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> NydusResult: """Run license utility on local vm with activation key :param activation_key: Key value passed back from license plesk op on hfs executor :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data """ LOG.info("install plesk license on '%s'", str(self)) op_name = 'license_plesk' command = 'license.exe' full_command = self.get_path_plesk(command) LOG.info("full command: %s", full_command) cmd = '"{full_command}" --install {activation_key}'.format(full_command=full_command, activation_key=activation_key) exit_code, outs, errs = run_uapi_command(cmd, 'set Plesk License', 'license_plesk', use_shell=True) return self._result_handler(exit_code, outs, errs, op_name, intermediate_result) # pylint: disable=too-many-locals def enable_plesk(self, vm_ip: str, vm_resource: str, plesk_user: str, plesk_pass: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> NydusResult: """Enable plesk on local server :param vm_ip: External IP address of the server :param vm_resource: The resource name for the third-party hosting provider :param plesk_user: User name to be used on Plesk instance :param plesk_pass: Password to be used on Plesk instance :param intermediate_result: Dict containing metadata for retries :raises DecryptError: if there is a problem with decrypting the password :return: tuple with success, error data """ LOG.info("enable plesk for VM IP %s", vm_ip) op_name = self.OP_ENABLE_PLESK password_arg = list2cmdline([self.decrypt(plesk_pass)]) ops_map = self.PLESK_OPS_RESOURCE_ATTRIBUTE_MAP[ResourceType(vm_resource)][op_name] init_conf_cmd = self.get_path_plesk('init_conf.exe') check_configured_cmd = '"{}" --check-configured'.format(init_conf_cmd) exit_code, outs, errs = run_uapi_command(check_configured_cmd, "Check Plesk configured", op_name, use_shell=True) res = self._result_handler(exit_code, outs, errs, op_name, intermediate_result) if isinstance(res, Retry): return res if not res[0]: # Plesk is not configured init_conf_setup_flag = '--init' else: # Plesk is configured init_conf_setup_flag = '--update' enable_cmds = [ ('set minimum password strength', '"{server_pref}" -u -min_password_strength medium'.format( server_pref=self.get_path_plesk('server_pref.exe'))), ('setup Plesk', ops_map[self.SETUP_CMD].format( init_conf_cmd='"{}"'.format(init_conf_cmd), init_conf_setup_flag=init_conf_setup_flag, plesk_user=plesk_user, password=password_arg)), ('set Poweruser', '"{poweruser_cmd}" --on'.format( poweruser_cmd=self.get_path_plesk('poweruser.exe'))), ('reconfigurator', '"{reconfigurator}" /check=Services /no-gui'.format( reconfigurator=self.get_path_plesk('reconfigurator.exe', ['admin', 'bin']))), ('Repair sslcerts', '"{}" repair web -sslcerts'.format(self.get_path_plesk('plesk.exe'))) ] exit_code, outs, errs = run_multiple_uapi_commands(enable_cmds, op_name, omit_string=password_arg) return self._result_handler(exit_code, outs, errs, op_name, intermediate_result) def site_list_plesk(self, *args: Any, intermediate_result: Dict[str, Any] = None) -> Any: """ Retrieve a list of Plesk sites :param intermediate_result: Dict containing metadata for retries :return: list of sites or tuple with error data """ LOG.info("get site list") op_name = 'site_list_plesk' plesk_dir_arg = [str(self.get_plesk_dir())] exit_code, outs, errs = run_powershell_file(SCRIPT_BASEPATH / 'powershell' / 'plesk_site_list.ps1', op_name, False, plesk_dir_arg) if exit_code != 0: return self._result_handler(exit_code, outs, errs, op_name, intermediate_result) logging.info(outs) return json.loads(outs) if outs else '' def server_prep_plesk(self, *args: Any, intermediate_result: Dict[str, Any] = None) -> NydusResult: """ Install Plesk on a server :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data """ LOG.info("install plesk on '%s'", str(self)) op_name = 'server_prep_plesk' exit_code, outs, errs = run_powershell_file( SCRIPT_BASEPATH / 'powershell' / 'plesk_server_prep.ps1', 'Prep Plesk') return self._result_handler(exit_code, outs, errs, op_name, intermediate_result) def get_client_plesk(self, *args: Any, intermediate_result: Dict[str, Any] = None) -> Any: """ Get Plesk SSO URL :param intermediate_result: Dict containing metadata for retries :return: SSO URL string or tuple with error data """ op_name = 'get_client_plesk' exit_code, outs, errs = runCommand(["plesk", "bin", "admin", "--get-login-link"], 'get sso link') if exit_code != 0: return self._result_handler(exit_code, outs, errs, op_name, intermediate_result) links = outs.split('\n') sso_url = str(links[0]) return self.encrypt(sso_url).decode('utf-8') def change_hostname_plesk(self, hostname: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> NydusResult: """ Change hostname on Plesk :param hostname: New name to be assigned to the host :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data """ op_name = 'change_hostname_plesk' rcmd = '"{plesk_path}" --update -hostname {hostname}'.format(plesk_path=self.get_path_plesk('server_pref.exe'), hostname=hostname) exit_code, outs, errs = run_uapi_command(rcmd, 'set Plesk hostname', op_name, use_shell=True) return self._result_handler(exit_code, outs, errs, op_name, intermediate_result) def change_admin_password_plesk(self, plesk_admin_pass: str, *args: Any, intermediate_result: Dict[str, Any] = None) -> NydusResult: """ Change admin password on Plesk :param plesk_admin_pass: Encrypted password for Plesk admin user :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data """ op_name = 'change_admin_password_plesk' password = self.decrypt(plesk_admin_pass) # Perform operations on Control Panel command_kwargs = {'omit_string': password} plesk_bin = self.get_path_plesk(None, ['bin', 'admin']) command = [plesk_bin, '--set-admin-password', '-passwd', password] exit_code, outs, errs = run_uapi_command( command, 'set Plesk admin password', op_name, **command_kwargs) return self._result_handler(exit_code, outs, errs, op_name, intermediate_result) def _check_for_dll_conflict(self, exit_code: int, result: Dict[str, Any], intermediate_result: Dict[str, Any] = None) -> NydusResult: """ Check to see if the output of the previous command indicates that a DLL version conflict exists and, if so attempt remediation. :param exit_code: The exit code from the previous command :param result: The result dictionary from the previous command :param intermediate_result: Dict containing metadata for retries :return: A 2-tuple if no remediation is necessary, otherwise a 3-tuple where the last element is a retry interval in seconds """ if exit_code == 0: if intermediate_result is not None and intermediate_result.get('delete_log_file', False): try: os.unlink(PLESK_FIX_DLL_CONFLICT_SCRIPT_LOG_FILE) except Exception: # pylint: disable=broad-except pass # We don't care if we're trying to delete a file that doesn't exist return True, result if has_dll_version_conflict(result.get('errs', '')): LOG.error("DLL conflict detected; attempting to remediate") code, _, _ = start_powershell_file(SCRIPT_BASEPATH / 'powershell' / 'plesk_fix_dll_conflict.ps1') if code == 0: if intermediate_result is None: intermediate_result = {} intermediate_result['delete_log_file'] = True return Retry(intermediate_result) if code == 0 else (False, result) return False, result def _result_handler(self, exit_code: int, outs: str, errs: str, op_name: str, intermediate_result: Dict[str, Any] = None) -> NydusResult: """Take the result from a run command and check for retryable errors. If found, return a Retry. If not, return a Nydus result tuple. :param exit_code: The exit code from the executed command :param outs: The stdout output from the executed command :param errs: The stderr output from the executed command :param op_name: The name of the op :param intermediate_result: Dict containing metadata for retries :return: A Nydus result tuple, or Retry """ success, result = self.build_result_from_cmd_output(exit_code, outs, errs, op_name, op_name + " succeeded") if not success: dll_res = self._check_for_dll_conflict(exit_code, result, intermediate_result=intermediate_result) if isinstance(dll_res, Retry): return dll_res return super()._result_handler(exit_code, outs, errs, op_name, intermediate_result) return success, result class Windows2016Plesk(Windows2016, WindowsPlesk): pass class Windows2019Plesk(Windows2019, WindowsPlesk): pass class Windows2022Plesk(Windows2022, WindowsPlesk): pass powershell/plesk_fix_dll_conflict.ps1000064400000001751147205401360014063 0ustar00$logFile = "C:\Windows\TEMP\plesk-fix-dll-conflict.log" $sourceDll = "C:\Windows\System32\vcruntime140.dll" function get-timestamp { get-date -format "yyyy-MM-dd HH:mm:ss.ffffff" } function log([string]$msg) { echo "$(get-timestamp): $msg" >>$logFile } remove-item -ea silentlycontinue $logFile log "BEGIN" if (test-path -pathtype leaf $sourceDll) { start-sleep 2 log "stopping nydus-ex-api" stop-service -ea continue -name nydus-ex-api 2>&1 >>$logFile log "stopping nydus-ex" stop-service -ea continue -name nydus-ex 2>&1 >>$logFile copy-item -ea continue $sourceDll C:\nydus\pyvenv\Scripts 2>&1 >>$logFile copy-item -ea continue $sourceDll C:\nydus\var\ops\pyvenv\Scripts 2>&1 >>$logFile start-sleep 2 log "starting nydus-ex" start-service -ea continue -name nydus-ex 2>&1 >>$logFile log "starting nydus-ex-api" start-service -ea continue -name nydus-ex-api 2>&1 >>$logFile } else { log "unable to find $sourceDll" } log "END" powershell/plesk_server_prep.ps1000064400000003267147205401360013121 0ustar00#Check to see if plesk install currently running $process = Get-WmiObject -query "select * from win32_process where " + ` "name like 'plesk-installer%' or name like 'parallels_installer_Microsoft_%'" if($process){ $process | %{$_.Terminate()} } #Remove any old installs Get-WmiObject -query "select * from win32_product where name like '%Plesk%'" | %{$_.uninstall()} #Download Installer powershell.exe Invoke-WebRequest https://hfs-public.secureserver.net/-/Windows/plesk-installer.exe ` -UseBasicParsing -OutFile C:\Windows\Temp\plesk-installer.exe #Attempt clean install. try{ C:\Windows\Temp\plesk-installer.exe --select-product-id=panel --select-release-latest ` --skip-components-check --ignore-key-errors --no-space-check --installation-type=typical }catch{ #Verify Mysql startup Get-Service | ?{$_.name -eq 'Mysql56' -and $_.Status -ne 'Running'} | Start-Service "Mysql56" | %{ $service=gwmi win32_service -filter "name='$_' and not State='Running'" if($service){ $service.ChangeStartMode("Automatic") $service.startservice() } } #Attempt to fix install C:\Windows\Temp\plesk-installer.exe --select-product-id=panel --select-release-latest ` --skip-components-check --ignore-key-errors --no-space-check --upgrade-installed-components } Start-sleep -seconds 60 $verifyInstallLog = select-string -path C:\ParallelsInstaller\autoinstaller3.log -Pattern "The changes were applied successfully." -CaseSensitive if((test-path C:\ParallelsInstaller\ai_action_time.xml) -and $verifyInstallLog){ "Plesk Successfully Installed" exit 0 } "ERROR - Plesk DID NOT INSTALL PROPERLY" exit 1 powershell/plesk_site_list.ps1000064400000002271147205401360012556 0ustar00$pleskDir=$args[0] $listOfVals=@{'FTP Login'='ftp_login'; 'IP Address'='ip_address'; 'Disk space used by httpdocs'='diskused'; 'Hosting type'='webspace'}; if(test-path "$($pleskDir)\admin\bin\subscription.exe"){ $allSubscriptions=&"$($pleskDir)\admin\bin\subscription.exe" --list; $allSites=&"$($pleskDir)\admin\bin\site.exe" --list; $dataToList=@() $dataToSend=@{} $allSites | %{ $siteName=$_; $dataObj = New-Object -TypeName PSObject $dataObj | Add-Member -MemberType NoteProperty -Name 'name' -Value $($siteName) $siteInfo=&"$($pleskDir)\admin\bin\site.exe" --info $siteName; $siteInfo | %{ $dataArr=[regex]::split($_,'\s\s+'); if($dataArr.length -eq 2){ $key=($dataArr[0]).trim(':'); if($listOfVals.ContainsKey($key)){ $val=$dataArr[1]; $dataObj | Add-Member -MemberType NoteProperty -Name $($listOfVals.Get_Item($key)) -Value $($val) } } } $dataToList+=$dataObj } $dataToSend['sites']=$dataToList $dataToSend['subscriptions']=@($allSubscriptions) $dataToSend | convertTo-json -Compress } __pycache__/__init__.cpython-38.pyc000064400000000425147205401360013146 0ustar00U afM@sddlmZeejZdS))PathN)pathlibr__file__parentresolveZSCRIPT_BASEPATHrrZ/opt/nydus/tmp/pip-target-53d1vnqk/lib/python/customer_local_ops/control_panel/__init__.pys __pycache__/cpanel.cpython-38.pyc000064400000020377147205401360012661 0ustar00U af\%@sddlmZmZmZmZddlZddlmZmZm Z ddl m Z e e ZdZdZGdddZGd d d eZGd d d eZdS) )DictAnyListTupleN)OpsOpType ResourceType)retryic @sTeZdZdZdZdZejeediiejeediiej eediiej eediiiZ dS)OSCPanelzWMixin class representing common functionality and constants for CPanel between all OSescpanel_activateZrun_installatron_repairFN) __name__ __module__ __qualname____doc__ZOP_CPANEL_ACTIVATEZRUN_INSTALLATRON_REPAIRrZ OPENSTACKZOVHZ VIRTUOZZO_VMZOPENSTACK_HOSTINGCLOUDZ!CPANEL_OPS_RESOURCE_ATTRIBUTE_MAPrrX/opt/nydus/tmp/pip-target-53d1vnqk/lib/python/customer_local_ops/control_panel/cpanel.pyr s8r c@seZdZdZejZeee fe e e eee ffdddZ eee fe e dddZ eddd d d eee fe eee fe d d dZee e dddZeeee e e efdddZeee e dddZeeed d d eee eee fe dddZee e dddZee e dddZee e ddd Zeee e e eee ffd!d"d#Zee e dd$d%Zee e dd&d'Zd S)(CPanelzCustomer Local ops for CPanel)payloadargsreturncGs2tdd}||d}||}|||S)aConfigures the mail transfer agent for cPanel. This is a bit different. If the user picks cpanel, we don't want to do the regular os_op configureMTA (exim conflicts with sendmail) :param payload: A dict containing input data zCpanel.configure_mta start configure_mtaos_op)LOGinfo get_os_opZconfigure_mta_cpanelbuild_result_from_other_result)selfrrop_nameZcp_os_op_instance os_resultrrrr/s   zCPanel.configure_mtacGs2|d}d}tdt||||}||S)zChanges the user password via an op for the local operating system :param payload: A dict containing input data rchange_passwordz@CPanel.change_password is NOOP, deferring to OS operation: %s.%s)rrstrrr )rrrrros_op_instancerrrr =s  zCPanel.change_passwordi,)intervaltimeoutNintermediate_result)rrr'rc Gstd|d}d}tdt||z||}Wn<tk rr}zd|dt||fWYSd}~XYnX|dkr||}||} td| j| j | j | js| ||S|j |d |d }| ||S) aChanges the server hostname via an op for the local operating system :param payload: A dict containing input data :param intermediate_result: intermediate result for storing the cPanel set-hostname result while waiting for the lock file to clear zCPanel.change_hostname startrchange_hostnamez7CPanel.change_hostname deferring to OS operation: %s.%sFNz3CPanel.change_hostname os_op_result - %s - %s - %shostnamer&) rrr!rAttributeErrorZbuild_result_dictr(get_result_datasuccessoutserrsrZchange_hostname_cpanel) rrr'rrrr"exrdatarrrr(Hs& ,   zCPanel.change_hostname)rrrcGsLd}||}|}||}|jsHtd|j|j|j|||S|S)zGets the cPanel public IP for this server :param os_op: Operating system customer_local_ops class of target server get_public_ipz1CPanel.get_public_ip os_op_result - %s - %s - %s) rZget_public_ip_cpanelr,r-rrr.r/rrrrrr"rr1rrrr2hs   zCPanel.get_public_ip)r private_addrsrrcGs$d}||}||}|||S)zMarks the server IPs as reserved :param os_op: Operating system customer_local_ops class of target server :param private_addrs: A list of IPs to mark mark_internal_addresses)rZmark_internal_addresses_cpanelr)rrr4rrr"rrrrr5vs  zCPanel.mark_internal_addresses)rcpanel_public_iprrcGs$d}||}||}|||S)zEnables cPanel functionality for this server :param os_op: Operating system customer_local_ops class of target server :param cpanel_public_ip: The cPanel public IP for the server cpanel_enable)rr7r)rrr6rrr"rrrrr7s  zCPanel.cpanel_enable)r vm_resourcerr'rcGs(d}||}|j||d}|||S)aActivates cPanel license for this server. If another process is running cpkeyclt, this one will wait and retry to ensure a successful licensing. :param os_op: Operating system customer_local_ops class of target server :param vm_resource: The resource name for the third-party hosting provider :param intermediate_result: an intermediate result r r&)rr r)rrr8r'rrr"rrrrr s  zCPanel.cpanel_activatecGs"d}||}|}|||S)zGenerates and sets a random mysql password for cPanel :param os_op: Operating system customer_local_ops class of target server set_mysql_password)rZset_mysql_password_cpanelrrrrrr"rrrrr9s zCPanel.set_mysql_passwordcGs"d}||}|}|||S)zxRe-secures the /tmp directory :param os_op: Operating system customer_local_ops class of target server enable_secure_tmp)rZenable_secure_tmp_cpanelrr:rrrr;s zCPanel.enable_secure_tmpcGs"d}||}|}|||S)zPre-installs and prepares cPanel on the server :param os_op: Operating system customer_local_ops class of target server cpanel_prep)rr<rr:rrrr<s zCPanel.cpanel_prep)r from_ip_addrrrcGs$d}||}||}|||S)zAllow cPanel access from customer IP :param os_op: Operating system customer_local_ops class of target server :param from_ip_addr: The IP address from which the customer will access the cPanel instance hulk_whitelist)rZhulk_whitelist_cpanelr)rrr=rrr"rrrrr>s  zCPanel.hulk_whitelistcGs8d}||}|}||}|js2|||S|jS)zjSet cPanel hash :param os_op: Operating system customer_local_ops class of target server get_hash)rZget_hash_cpanelr,r-rresultr3rrrr?s   zCPanel.get_hashcGs8d}||}|}||}|js2|||S|jS)zoGet cPanel API Token :param os_op: Operating system customer_local_ops class of target server get_api_token)rZget_api_token_cpanelr,r-rr@r3rrrrAs   zCPanel.get_api_token)r rrrrZ CONTROL_PANELZop_typerr!rrboolrr r r(r2rr5r7$RETRY_CPANEL_ACTIVATE_RETRY_INTERVALRETRY_CPANEL_ACTIVATE_TIMEOUTr r9r;r<r>r?rArrrrr*s2*         $  rcseZdZfddZZS)CPanelExceptioncs||_||_t|dS)N)r/r.super__init__)rr.r/ __class__rrrGszCPanelException.__init__)r rrrG __classcell__rrrHrrEsrE)typingrrrrloggingZcustomer_local_opsrrrZcustomer_local_ops.util.retryr getLoggerr rrDrCr r ExceptionrErrrrs  9__pycache__/ispconfig.cpython-38.pyc000064400000006314147205401360013373 0ustar00U afz @sXddlmZmZmZddlZddlmZmZee Z GdddZ GdddeZ dS))DictAnyTupleN)OpsOpTypec@seZdZdZdS) OSISPConfigzZMixin class representing common functionality and constants for ISPConfig between all OSesN)__name__ __module__ __qualname____doc__r r [/opt/nydus/tmp/pip-target-53d1vnqk/lib/python/customer_local_ops/control_panel/ispconfig.pyr src@seZdZejZdZeee fe e dddZ eee fe e e fdddZ eee fe e ddd Zeee fe e e fdd d ZdS) ISPConfigN)payloadargsreturncGstd|d}d}|jdkr,|||_tdt||||\}}|s`|||f|Std|j|d|dd |fS) zChanges the server hostname via an op for the local operating system and an op for ispconfig :param payload: A dict containing input data zISPConfig.change_hostname startos_opchange_hostnameNz:ISPConfig.change_hostname deferring to OS operation: %s.%sz!Change the hostname in ISPConfig.Tzhostname changed successfully) LOGinfoos_op_instance get_os_opstr_change_os_hostnamebuild_result_from_other_resultZchange_ispconfig_hostnamebuild_result_dictselfrrrZop_namesuccess os_resultr r r rs      zISPConfig.change_hostname)rrcCs4|j|}||}td|j|j|j|S)zChanges the server hostname via an op for the local operating system :param payload: A dict containing input data z6ISPConfig.change_hostname os_op_result - %s - %s - %s) rrget_result_datarrroutserrsas_tuplerrr datar r r r)s  zISPConfig._change_os_hostnamecGstd|d}d}tdt|||jdkr>|||_||\}}|s`|||f|S|j||d|dd|fS) zChanges the user password via an op for the local operating system and an op for ispconfig :param payload: A dict containing input data zISPConfig.change_password startrchange_passwordz:ISPConfig.change_password deferring to OS operation: %s.%sNTzpassword changed successfullyr) rrrrr_change_os_passwordrZchange_ispconfig_passwordrrr r r r'3s   zISPConfig.change_passwordcCs4|j|}||}td|j|j|j|S)zChanges the user password via an op for the local operating system :param payload: A dict containing input data z6ISPConfig.change_password os_op_result - %s - %s - %s) rr'r!rrrr"r#r$r%r r r r(Hs  zISPConfig._change_os_password)rr r rZ CONTROL_PANELZop_typerrrrrrboolrr'r(r r r r rs  r) typingrrrloggingZcustomer_local_opsrr getLoggerrrrrr r r r s  __pycache__/linux_cpanel.cpython-38.pyc000064400000044213147205401360014073 0ustar00U afV@sddlmZddlmZmZmZmZddlZddlZddlZddl Z ddl Z ddl m Z ddl mZmZddlmZmZmZmZmZmZmZmZmZddlmZmZddlmZdd lm Z m!Z!dd l"m#Z#m$Z$m%Z%m&Z&dd l'm(Z(m)Z)e*e+Z,d Z-Gd ddeeZ.Gdddee.Z/Gdddee/Z0Gdddee/Z1Gdddee/Z2Gdddee/Z3Gdddee.Z4Gdddee4Z5Gdddee4Z6dS))datetime)DictAnyListTupleN) ShortUUID)OpType ResourceType) AlmaLinux8 AlmaLinux9LinuxCentOSCentOS6CentOS7DebianDebian8 Ubuntu1604)CPanelExceptionOSCPanel)random_password) runCommandrun_uapi_command) replace_lineedit_file_lines create_filereplace_file_lines_multiple)RetryRETRYc@seZdZdZddgZdZdZejZ e e e fe e dddZ ee e e eee e e ffd d d Zd d e e e e e fe dddZe e dddZee e eee fdddZe e e dddZd d e e e e e fe dddZe e dddZe e ddd Ze e dd!d"Zed#d$d%Zed#d&d'Zed#d(d)Zed#d*d+Zed#d,d-Zed#d.d/Zed#d0d1Z ed#d2d3Z!e e eee e e ffd4d5d6Z"e e dd7d8Z#e e dd9d:Z$d S); LinuxCPanelz CPanel Customer Local Ops for the Linux OS. All function names should contain 'cpanel' so as not to override the OS ops znydus-exz nydus-ex-apiz*/etc/cpanel/local/ignore_outdated_servicesz-/var/cpanel/.application-locks/UpdateHostname)payloadargsreturnc Gstdd}zPtjtdddd}td||d}|d k r\d }td |t|d |WnLtk r}z.t d t |d| dt ||fWYSd }~XYnXtd| dgd|S)zlConfigures the mail transfer agent for cPanel :param payload: A dict containing input data z&LinuxCPanel.configure_mta_cpanel startconfigure_mta_cpanelZdefaultmailactionzdefaultmailaction=fail FmatchreplaceZ firstword/var/cpanel/cpanel.configZ relay_addressNz/etc/exim.conf.localz+LinuxCPanel.configure_mta_cpanel writing %sa# @AUTH@ @BEGINACL@ @CONFIG@ @DIRECTOREND@ @DIRECTORMIDDLE@ @DIRECTORSTART@ @ENDACL@ @RETRYEND@ @RETRYSTART@ @REWRITE@ @ROUTEREND@ @ROUTERSTART@ send_to_smart_host: driver = manualroute route_list = !+local_domains %s transport = remote_smtp @TRANSPORTEND@ @TRANSPORTMIDDLE@ @TRANSPORTSTART@ z:cp_os_op LinuxCPanel configure_mta_cpanel result(fail): %sz6LinuxCPanel.configure_mta_cpanel running buildeximconfz/scripts/buildeximconfzbuild exim config) LOGinfo functoolspartialrrgetr Exceptionerrorstrbuild_result_dict_run_uapi_command)selfr r!op_nameZset_tgtZrelayZexim_conf_pathexr6^/opt/nydus/tmp/pip-target-53d1vnqk/lib/python/customer_local_ops/control_panel/linux_cpanel.pyr#(s*     , z LinuxCPanel.configure_mta_cpanel)cmd_list descriptionr4r"cCst|||\}}}|dkrtd|dd|krNd|krNd||||fSdd|krvd|krvd||||fSd ||||fSd||||fS) zRuns a local cPanel command :param cmd_list: A list of commands to run :param description: A description of the command(s) to be run :param op_name: The name of the op calling this function rzcp_op_result: success! %sz uapi --user= z errors: ~Fzwhmapi1 z result: 1T)rr)debugjoinr1)r3r8r9r4 exit_codeoutserrsr6r6r7r2Us zLinuxCPanel._run_uapi_commandNintermediate_result)hostname_rAr"cGsftd|dkr8|d|gdd}|ds0|Sd|i}tj|jr^td|jt|d S|dS) azChanges the server hostname via a command for the local operating system :param hostname: the new server hostname :param intermediate_result: Nydus intermediate result for storing the set-hostname result while waiting for the cPanel lock file to clear :returns: the result of the set-hostname cPanel call, in Nydus/CLO result format z(LinuxCPanel.change_hostname_cpanel startNz"/usr/local/cpanel/bin/set_hostnamezset cpanel hostchange_hostname_cpanelrZset_hostname_resultzCcPanel hostname change lock file exists (%s), will try again later.r@)r)r*r2ospathexistsHOSTNAME_CHANGE_LOCK_FILEr)r3rBrArCresultr6r6r7rDks  z"LinuxCPanel.change_hostname_cpanel)r!r"cGsDd}dddddg}t|d\}}}|dkr2|}|Sd ||||fS) z)Gets the cPanel public IP for this serverget_public_ip_cpanelwgetz-q-O-z http://www.cpanel.net/showip.cgizGet cPanel outbound IPrFrr1)r3r!r4commandr=r>r?Z public_ipr6r6r7rJsz LinuxCPanel.get_public_ip_cpanel) private_addrsr!r"c Gsrd}ztdd|tddWn<tk r^}zd|dt||fWYSd}~XYnXd |d d|fS) z^Marks the server IPs as reserved :param private_addrs: A list of IPs to mark mark_internal_addresses_cpanelz/etc/reservedips z/etc/reservedipreasonsztk r}zd!|d t||fWYSd"}~XYnXd|d#d |fS)$z{Enables cPanel functionality for this server :param cpanel_public_ip: The cPanel public IP for the server cpanel_enablezcpanel_public_ip- %s/etc/wwwacct.confZADDRzADDR %s Tr$/var/cpanel/mainipz%sz*/var/cpanel/activate/2012-07.v01.EULACPWHMr(z/etc/.whostmgrftZsedz-iz-s/rpmup_allow_kernel=0/rpmup_allow_kernel=1/gr'zenable automatic kernel updatesz-Automatic kernel updates result: %s: %s -- %sz?s/allow_deprecated_accesshash=0/allow_deprecated_accesshash=1/gzenable accesshashz&Enable accesshash result: %s: %s -- %sZserviceZcpanelZrestartzrestart cpanelz#restart cpanel result: %s: %s -- %s) Z appconfigZemail_archivingZemail_autodiscoveryZ log_archivingZquery_apache_for_nobody_sendersZserver_usage_analyticsZservers_usage_analyticsZsmtp_restrictionsZtrust_x_php_scriptz/var/cpanel/activate/features/z1USER=root MODIFIED=%s TIMESTAMP=%s INTERFACE=GUI z/scripts/restartsrv_queueprocdzrestart queueprocdz'Restart queueprocd result: %s: %s -- %sFNzCPanel enabled successfully)r)r;rr+r,rrEremoverSrrrFr<rnowctimer1r0) r3rTr!r4r=r>r?featurer5r6r6r7rUsV      ,zLinuxCPanel.cpanel_enable) vm_resourcer!rAr"c Gs|j}tdgd\}}}d|kr$tS|dkr>d||||fS|jt||}||jrtdddd gd \}}}td |||td d dgd\}}}td|||tdddgd\}}}td|||tdddgd\}}}td|||dS)a9Activates cPanel license for this server. If another process is running cpkeyclt, this one will wait and retry to ensure a successful licensing. :param vm_resource: The resource name for the third-party hosting provider :param intermediate_result: an intermediate result z/usr/local/cpanel/cpkeycltzactivate licensez-A License check appears to already be runningrFrmz-frz/usr/local/installatron/libz#/usr/local/installatron/etc/php.inizrm installatron php.iniz-rm installatron php.ini results: %s: %s -- %sZcurlrLz4https://data.installatron.com/installatron-plugin.shzcurl installatron-pluginz-curl installatron-plugin result: %s: %s -- %schmodz+xzinstallatron-plugin.shz chmod pluginz!chmod plugin result: %s: %s -- %sz./installatron-plugin.sh-fz--quickzrebuild pluginz#rebuild plugin result: %s: %s -- %sN) ZOP_CPANEL_ACTIVATErrr1Z!CPANEL_OPS_RESOURCE_ATTRIBUTE_MAPr ZRUN_INSTALLATRON_REPAIRr)r;) r3r\rAr!r4r=r>r?Zops_mapr6r6r7cpanel_activates*      zLinuxCPanel.cpanel_activatecGsnd}t}tdd|gd|d\}}}|dkr>d||||fStdgd \}}}|dkrjd||||fSd S) z5Generates and sets a random mysql password for cPanelset_mysql_password_cpanelz/scripts/mysqlpasswdrootzmysql password)Z omitStringrFz/scripts/mysqlconnectioncheckz restart mysqlN)rrr1)r3r!r4passwordr=r>r?r6r6r7ras z%LinuxCPanel.set_mysql_password_cpanelcGs8d}tdddgd\}}}|dkr4d||||fSdS) zRe-secures the /tmp directoryenable_secure_tmp_cpanelr]r_&/var/cpanel/version/securetmp_disabledzre-secure tmp dirrFNrN)r3r!r4r=r>r?r6r6r7rdsz$LinuxCPanel.enable_secure_tmp_cpanelc Gsd}zD||||||||Wn<tk r}zd| |j |j |fWYSd}~XYnX| dd|S)z.Pre-installs and prepares cPanel on the server cpanel_prepFNzcpanel_prep completer() _check_hostname_cpanel_yum_update_cpanel_download_cpanel'_exclude_nydus_from_auto_restart_cpanel_install_cpanel_install_installatron_cpanel_config_cpanel_disable_secure_tmp_cpanelrr1r>r?)r3r!r4r5r6r6r7rf s ,zLinuxCPanel.cpanel_prep)r"cCs8ttd}t|ddkr4tdd|dS)a!Checks that the server's fully qualified hostname is valid for CPanel `hostname` -> "PT-Test.secureserver.net" but `hostname -f` -> "localhost". /etc/hostname: pt-test.secureserver.net /etc/hosts: 127.0.0.1 localhost PT-Test.secureserver.net # cloud-controlled; do not change 10.192.28.50 pt-test.secureserver.net pt-test CHANGE: removed the PT-Test on the 127.0.0.1 line. `hostname` -> "PT-Test.secureserver.net" `hostname -f` -> "pt-test.secureserver.net" r.r(zJcPanel installation requires a fully-qualified hostname ({} is not enough)T)socket gethostbyaddr gethostnamelensplitrformat)r3rBr6r6r7rgs z"LinuxCPanel._check_hostname_cpanelcCs$|\}}}|dkr t||dS)zcPerforms a yum update on the server :raises CPanelException: If the command fails rT)Z _yum_updaterr3r=r>r?r6r6r7rh0s zLinuxCPanel._yum_update_cpanelcCs.tddddgd\}}}|dkr*t||dS)z^Downloads cPanel to the server :raises CPanelException: If the command fails rKz)https://securedownloads.cpanel.net/latestrL/root/cpinstallzdownload cpinstallrTrrrwr6r6r7ri:s  zLinuxCPanel._download_cpanelcCs8tjtj|jdddd|j}t|j|dS)aExclude Nydus services from cPanel's auto restarts. cPanel restarts services with outdated dependencies during install. It does this very generally, and Nydus services are included and stopped in the middle of install, causing terminal failure. This method adds Nydus services to the exclusion list so they are not restarted. See also, on a cPanel system: - /usr/local/cpanel/scripts/find_outdated_services - /usr/local/cpanel/Cpanel/ProcessCheck/Outdated.pm T)exist_okz%s rR)rEmakedirsrFdirnameAUTO_RESTART_EXCLUSION_FILEr<AUTO_RESTART_EXCLUDE_SERVICESr)r3contentr6r6r7rjGs z3LinuxCPanel._exclude_nydus_from_auto_restart_cpanelcCs*tddgd\}}}|dkr&t||dS)z]Installs cPanel on the server :raises CPanelException: If the command fails Zbashrxzinstall cpanelrTryrwr6r6r7rkZs zLinuxCPanel._install_cpanelcCs0tdddddgd\}}}|dkr,t||dS) zjInstalls Installatron plugin on the server :raises CPanelException: If the command fails rpmz-Uz-hz-vzIhttp://data.installatron.com/installatron-plugin-cpanel-latest.noarch.rpmzinstall installatronrTryrwr6r6r7rlds  z(LinuxCPanel._install_installatron_cpanelc Cspz8dddddd}td|d}tj|r6t|Wn2tk rj}ztd t||W5d }~XYnXd S) zwAdds initial cPanel configuration :raises CPanelException: If there is a problem running any commands zNS2 ns2.secureserver.net zNS ns1.secureserver.net z ETHDEV eth0 z-CONTACTEMAIL root@cpaneltmp.secureserver.net zADDR )ZNS2ZNSZETHDEVZ CONTACTEMAILz^ADDR .*rVrWr(NT)rrErFrGunlinkrSrr0)r3Z replace_dictZ mainipfiler5r6r6r7rmqs  "zLinuxCPanel._config_cpanelcCs*tddgd\}}}|dkr&t||dS)zeDisables noexec on the /tmp directory :raises CPanelException: If the command fails touchrezdisable noexec /tmprTryrwr6r6r7rns z&LinuxCPanel._disable_secure_tmp_cpanel) from_ip_addrr!r"cGs.d}td|gd\}}}|dk||||fS)zAllow cPanel access from customer IP :param from_ip_addr: The IP address from which the customer will access the cPanel instance hulk_whitelist_cpanelz/scripts/cphulkdwhitelistzcphulk whitelistrrN)r3rr!r4r=r>r?r6r6r7rsz!LinuxCPanel.hulk_whitelist_cpanelc Gsd}d}tj|r.tj|dkr.t|tj|shtddgd\}}}|dkrhd||||fSt|dd d }|}W5QRXd | d S) zSet cPanel hashget_hash_cpanelz/root/.accesshashrz'/usr/local/cpanel/whostmgr/bin/whostmgrZsetrhashz generate hashFrutf-8)encodingr(rR) rErFrGgetsizerrr1openreadr<ru) r3r!r4rFr=r>r?ZhashfZcphashr6r6r7rs  zLinuxCPanel.get_hash_cpanelcGs*d}d}tjtd}||}tdddgd\}}}|dkrNd ||||fSt|} | d d } | D](} | |rptddd d j | dgdqptddddj |dgd\}}}|dkrd ||||fSt|} | d } t | t r| d}|dk r| |dSd |dd|fS)z_Get cPanel API Token. Revoke any existing nydus-generated tokens before generating a new token.get_api_token_cpanelZdashboard_generated_)lengthz/usr/local/cpanel/bin/whmapi1z--output=jsonprettyZapi_token_listzget api tokensrFdatatokensZapi_token_revokeztoken_name={old_token_name})old_token_namez revoke tokenZapi_token_createztoken_name={token_name}) token_namezgenerate api tokentokenNrr(z'Invalid token json returned from cpanel)rrandomSHORT_UUID_DEFAULT_LENGTHrr1jsonloadsr-keys startswithrv isinstancedictZencryptdecode)r3r!r4Z token_prefixZ token_suffixrr=r>r?Z tokens_jsonrrZ token_jsonZ token_datarr6r6r7rsB             z LinuxCPanel.get_api_token_cpanel)%__name__ __module__ __qualname____doc__r~r}rHrZCONTROL_PANEL_OPERATING_SYSTEMZop_typerr0rr#rrboolr2rDrJrQrUr`rardrfrgrhrirjrkrlrmrnrrrr6r6r6r7rsD-(    8  !     " rc@s eZdZdS) CentOSCPanelNrrrr6r6r6r7rsrc@seZdZddZdS) CentOS6CPanelcOstdSNNotImplementedErrorr3r!kwargsr6r6r7__init__szCentOS6CPanel.__init__Nrrrrr6r6r6r7rsrc@s eZdZdS) CentOS7CPanelNrr6r6r6r7rsrc@s eZdZdS)AlmaLinux8CPanelNrr6r6r6r7rsrc@s eZdZdS)AlmaLinux9CPanelNrr6r6r6r7rsrc@seZdZddZdS) DebianCPanelcOstdSrrrr6r6r7rszDebianCPanel.__init__Nrr6r6r6r7rsrc@s eZdZdS) Debian8CPanelNrr6r6r6r7rsrc@s eZdZdS)Ubuntu1604CPanelNrr6r6r6r7rsr)7rtypingrrrrr+loggingrErqrZ shortuuidrZcustomer_local_opsrr Z)customer_local_ops.operating_system.linuxr r r r rrrrrZ'customer_local_ops.control_panel.cpanelrrZcustomer_local_ops.utilrZcustomer_local_ops.util.executerrZcustomer_local_ops.util.helpersrrrrZcustomer_local_ops.util.retryrr getLoggerrr)rrrrrrrrrrr6r6r6r7s8  ,  :__pycache__/linux_ispconfig.cpython-38.pyc000064400000014231147205401360014607 0ustar00U af@sddlZddlZddlmZmZddlmZddlmZddl m Z m Z m Z m Z mZmZmZddlmZeeZGddde eZGd d d e eZGd d d e eZGd dde eZGdddeeZGdddeeZGdddeeZdS)N)DictAny) connection)OpType)LinuxCentOSCentOS6CentOS7DebianDebian8 Ubuntu1604) OSISPConfigc@seZdZdZejZdZee e fddddZ e ddddZ e jd d d Ze e e d d dZee e fe ddddZe ddddZee e fe dddZe d ddZe d ddZe e dddZdS)LinuxISPConfigz ISPConfig Customer Local Ops for the Linux OS. All function names should contain 'ispconfig' so as not to override the OS ops N)payloadreturncCs|d}||dS)zhChanges the server hostname for ispconfig :param payload: A dict containing input data hostnameN)_change_hostname_in_db)selfr new_hostnamera/opt/nydus/tmp/pip-target-53d1vnqk/lib/python/customer_local_ops/control_panel/linux_ispconfig.pychange_ispconfig_hostnamesz(LinuxISPConfig.change_ispconfig_hostname)rrcCsd|}|}z<|d|d}|||}|d|f|W5||XdS)zxChanges the server hostname for ispconfig in the database :param new_hostname: The new hostname string z-select config from server where server_id = 1rz1UPDATE server SET config = %s WHERE server_id = 1N)_get_database_connectioncursorcloseexecuteZfetchone_replace_hostname_in_configcommit)rrcnxrconfigrrrr s    z%LinuxISPConfig._change_hostname_in_db)rcCs&|}|}tj||ddd}|S)z.Returns an ispconfig MySQL database connectionz 127.0.0.1Z dbispconfig)userpasswordhostZdatabase)_get_sql_username_get_sql_passwordrMySQLConnection)rZ sql_usernameZ sql_passwordrrrrr3s  z'LinuxISPConfig._get_database_connection)rrrcCs,d}t||}|d}|||}|S)zReplaces hostname in ispconfig configuration and returns the new configuration string :param config: Configuration string in which to replace the hostname :param new_hostname: The new hostname string zhostname=(?P[\w\-.]*)r)research groupdictreplace)rrrpatternmatchZ old_hostnamerrrr;s    z*LinuxISPConfig._replace_hostname_in_config)rop_namercCs||}||dS)zChanges the user password for ispconfig :param payload: A dict containing input data :param op_name: The name of the op N)_get_new_password_change_password_in_db)rrr, new_passwordrrrchange_ispconfig_passwordGs z(LinuxISPConfig.change_ispconfig_password)r/rcCsD|}|}z|d|df|W5||XdS)zxChanges the server password for ispconfig in the database :param new_password: The new password string z:UPDATE sys_user SET passwort = md5(%s) WHERE username = %sZadminN)rrrrr)rr/rrrrrr.Ps z%LinuxISPConfig._change_password_in_dbcCs|d}||}|S)zqDecrypts the new password from an encrypted string :param payload: A dict containing input data encrypted_password)decrypt)rrr1r/rrrr-^s z LinuxISPConfig._get_new_passwordcCs |dS)z6Returns the MySQL database password from configurationZ db_password_get_config_valuerrrrr$gsz LinuxISPConfig._get_sql_passwordcCs |dS)z2Returns the MySQL database user from configurationZdb_userr3r5rrrr#ksz LinuxISPConfig._get_sql_username)keyrc CsPtddd}|}W5QRXtd|d}t||}|d}|S)zsRetrieves a value from configuration from its key :param key: The configuration key for the value z1/usr/local/ispconfig/interface/lib/config.inc.phpzutf-8)encodingz $conf['{0}']z#\s*=\s*[\'"](?P[^"\']*)["\']value)openreadr&escapeformatr'r()rr6fZ ispConfigr*r+r8rrrr4os  z LinuxISPConfig._get_config_value)__name__ __module__ __qualname____doc__rZCONTROL_PANEL_OPERATING_SYSTEMZop_typeZos_op_instancerstrrrrrr%rrr0r.r-r$r#r4rrrrrs   rc@seZdZddZdS)CentOSISPConfigcOstdSNNotImplementedErrorrargskwargsrrr__init__szCentOSISPConfig.__init__Nr>r?r@rJrrrrrC~srCc@s eZdZdS)CentOS6ISPConfigNr>r?r@rrrrrLsrLc@s eZdZdS)CentOS7ISPConfigNrMrrrrrNsrNc@s eZdZdS)DebianISPConfigNrMrrrrrOsrOc@seZdZddZdS)Debian8ISPConfigcOstdSrDrErGrrrrJszDebian8ISPConfig.__init__NrKrrrrrPsrPc@s eZdZdS)Ubuntu1604ISPConfigNrMrrrrrQsrQ)loggingr&typingrrZmysql.connectorrZcustomer_local_opsrZ)customer_local_ops.operating_system.linuxrrrr r r r Z*customer_local_ops.control_panel.ispconfigr getLoggerr>LOGrrCrLrNrOrPrQrrrrs  $  n__pycache__/linux_plesk.cpython-38.pyc000064400000043574147205401360013760 0ustar00U af[@sddlmZmZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z ddl mZmZmZddlmZmZmZmZmZmZmZmZmZmZmZmZmZmZm Z ddl!m"Z"ddl#m$Z$m%Z%m&Z&dd l'm(Z(e)e*Z+d Z,d Z-Gd d d ee"Z.Gdddee.Z/Gdddee/Z0Gdddee/Z1Gdddee/Z2Gdddee/Z3Gdddee.Z4Gdddee4Z5Gdddee4Z6Gdddee4Z7Gd d!d!ee4Z8Gd"d#d#ee4Z9Gd$d%d%ee4Z:Gd&d'd'ee4Z;Gd(d)d)e e4Zr.r0r6Z password_argZops_maprCZcheck_configured_cmdr8r9r:rDZ prep_cmdsr*r*r+ enable_pleskRsP       zLinuxPlesk.enable_plesk)r0r.rcGstdi}t|ddddd\}}}|dkrF||||d|S|d|d <|d }t|dd dd\}}}|dkr||||d |S|d|d <d dddd}t|d D]\} } d| i} t|d| ddd\}}}|dkr||||d|S|d} | D]V} dd| dD}td|t|dkr|d}||kr|d| ||<q| |d | <qtt ||S)zRetrieve a list of Plesk sites :param intermediate_result: Dict containing metadata for retries :return: list of sites or tuple with error data z get site listZ subscriptionz --listz"site_list_plesk: get_subscriptionsTZuseShellr Z subscriptionssitezsite_list_plesk: get_sitessitesZ ftp_login ip_addressZdiskusedZwebspace)z FTP Loginz IP addresszDisk space used by httpdocsz Hosting typenamez --info zsite_list_plesk: get_site_infocSsg|] }|qSr*)strip).0xr*r*r+ sz.LinuxPlesk.site_list_plesk..:zdata: %s) r%r&rr4r5split enumeratelogginglenjsondumps)r(r.r0Z site_listr8r9r:Zsite_cmdZrequired_fieldsirNZ site_dataoutputlinedatakeyr*r*r+site_list_plesksR          zLinuxPlesk.site_list_pleskc Gsxtdt|d}ddddg}|D]>\}}t|||dd\}}} |d kr$|j||| ||d Sq$|j||| ||d S) z Install Plesk on a server :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data zinstall plesk on '%s'server_prep_plesk)zDownload Pleskz2wget https://autoinstall.plesk.com/plesk-installer)z Modify Permszchmod +x plesk-installer)z Install Pleskzsh plesk-installer --select-product-id=plesk --installation-type Typical --select-release-latest --notify-email admin@example.com)zDelete Docker Interfacezip link del docker0Tr@rr-)r%r&r3rr5) r(r.r0r6ZprepCmdspurposecmdr8r9r:r*r*r+res zLinuxPlesk.server_prep_pleskcGs`t|ddddd\}}}|dkr:|j|||d|dS|d }t|d}||d S) z Get Plesk SSO URL :param intermediate_result: Dict containing metadata for retries :return: SSO URL string or tuple with error data Zadminz --get-login-linkz get sso linkTrLrget_client_pleskr-rMutf-8)rr4r5rYr3Zencryptdecode)r(r.r0r8r9r:linksZsso_urlr*r*r+rhs    zLinuxPlesk.get_client_plesk)hostnamer0r.rc GsDd}|ddd|}t|d|dd\}}}|j|||||dS) z Change hostname on Plesk :param hostname: The new server hostname :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data change_hostname_pleskrBz --updatez -hostname zset Plesk hostnameTr@r-)r4rr5) r(rlr.r0r6Zrcmdr8r9r:r*r*r+rmsz LinuxPlesk.change_hostname_plesk)plesk_admin_passr0r.rc GsNd}||}|d}|dd|g}t|d||d\}} } |j|| | ||dS)z Change admin password on Plesk :param plesk_admin_pass: Encrypted password for Plesk admin user :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data change_admin_password_pleskr?z--set-admin-passwordz-passwdzset Plesk admin password)rFr-)rIr4rr5) r(rnr.r0r6rEZ plesk_binr7r8r9r:r*r*r+ros    z&LinuxPlesk.change_admin_password_plesk) relay_addressr6r.rc Csn|dkr dSGdddt}ztd}td}d|}|j}tddd r} | D]f} || rvtj ||j }qT|| r||jkr|j }n||j krtj ||j }tj | qTW5QRX||j krt dd d d } | |W5QRXWnNt k rD} z.td || |jddt| ||dWYSd} ~ XYnXtd|d} |jdddg| |dS)zConfigure the postfix MTA. :param relay_address: IP address or host name of the mail relay to set :param op_name: name of the operation being executed :param intermediate_result: Dict containing metadata for retries Nc@seZdZdZdZdZdS)z1LinuxPlesk.configure_postfix..SearchStaterrXrWN)__name__ __module__ __qualname__ SEARCHINGSKIPPINGDONEr*r*r*r+ SearchStatesrwz^[ \t]*#[ \t]*relayhost[ \t]*=z^[ \t]*relayhost[ \t]*=zrelayhost = [{}] z/etc/postfix/main.cfT)Zinplaceari)encodingz.cp_os_op %s.configure_postfix result(fail): %srXr-zrestarting postfixz: restart postfixZ systemctlZrestartpostfix)rrecompilerJrtrmatchsysstdoutwritervruopen Exceptionr%errorr5r3r&run_command_and_handle_result)r(rpr6r.rwZ relayhost_cmtZrelayhost_blnkZrelayhost_linestatestreamrafex my_op_namer*r*r+configure_postfixs@          . zLinuxPlesk.configure_postfix)addressr0r.rcGs"|j|jddddd|gd|dS)aSet Plesk's outgoing e-mail IP address. This only works with Postfix mail server. See General #6 for more information: https://docs.plesk.com/en-US/obsidian/administrator-guide/mail/configuring-serverwide-mail-settings.59430/ :param address: IP address from which to send e-mail :param intermediate_result: Dict containing result data and meta data for retries :return: Nydus operation result Z mailserver)filenamez--set-outgoing-email-modez explicit-ipz-explicit-ipv4set_outgoing_email_ipr-)rr4)r(rr.r0r*r*r+r<s  z LinuxPlesk.set_outgoing_email_ip)N)rqrrrs__doc__rZCONTROL_PANEL_OPERATING_SYSTEMZop_type PLESK_DIR_1 PLESK_DIR_2r!r ZRETRYABLE_ERRSr3r,rrrr1rKrdrerhrmrorrr*r*r*r+rsZ     < 0        8 rc@sdeZdZegZd edddZd eee feeee fedddZ d eeee fedd d Z dS) CentOSPleskNrcCstd||j|d|dS)a Configure mail transfer agent on a CentOS-based Plesk server :param payload: Dict containing op params :param intermediate_result: Dict containing result data and meta data for retries :return: tuple with success, retry or error data z%s.configure_mtaCentOSPlesk.configure_mtar-r%r&do_configure_mtar(payloadZunusedr.r*r*r+ configure_mtaRs rrr6r.rcCs`td||j||d}t|ts,|ds0|S|d}td|j|||j|||dS)aNConfigure mail transfer agent on a CentOS-based Plesk server :param payload: Dict containing op params :param op_name: The name of the op, including the classname :param intermediate_result: Dict containing result data and meta data for retries :return: tuple with success, retry or error data z%s.do_configure_mtar-rrp%s %s start relay_address: %s r%r&install_postfix isinstancergetZ get_op_typevaluerr(rr6r.resultrpr*r*r+r[s  zCentOSPlesk.do_configure_mtar6r.rcCstd||d}|dddg|\}}}|dkrH|j|||||dS|d}|dd d d g|\}}}|dkr|j|||||dS|d }|dd d dg|\}}}|j|||||dS) Install postfix on a CentOS-based Plesk server :param op_name: The name of the op, including the classname :param intermediate_result: Dict containing result data and meta data for retries :return: tuple with success, retry or error data z%s.install_postfixz: yum_clean_allZyumcleanallrr-: remove_sendmail-yremovesendmail: install postfixinstallr{)r%r&Z_run_yum_commandr5)r(r6r.rr8r9r:r*r*r+rks zCentOSPlesk.install_postfix)NN)N)N) rqrrrsrr!rrrr3rrrr*r*r*r+rMs   rc@seZdZddZdS) CentOS6PleskcOstdSNNotImplementedErrorr(r0kwargsr*r*r+__init__szCentOS6Plesk.__init__Nrqrrrsrr*r*r*r+rsrc@s eZdZdS) CentOS7PleskNrqrrrsr*r*r*r+rsrc@s eZdZdS)AlmaLinux8PleskNrr*r*r*r+rsrc@s eZdZdS)AlmaLinux9PleskNrr*r*r*r+rsrc@s^eZdZd edddZd eeefeeeefedddZd eeeefedd d Z dS) DebianPleskNrcCstd|j|d|dS)a Configure mail transfer agent on a Debian-based Plesk server :param payload: Dict containing op params :param intermediate_result: Dict containing result data and meta data for retries :return: tuple with success, retry or error data DebianPlesk.configure_mtar-rrr*r*r+rs rrcCs^td|j||d}t|ts*|ds.|S|d}td|j|||j|||dS)aJConfigure mail transfer agent on a Debian-based Plesk server :param payload: Dict containing op params :param op_name: The name of the op, including the classname :param intermediate_result: Dict containing result data and meta data for retries :return: tuple with success, retry or error data DebianPlesk.do_configure_mtar-rrprrrr*r*r+rs  rrcCstd|d}tddddg|\}}}|dkrF|j|||||dS|d }t|tr^|S|d }|\}}}|j|||||dS) rDebianPlesk.install_postfixrzapt-getrrrrr-r{r)r%r&rr5_installrr)r(r6r.rr8r9r:rr*r*r+rs    r)NN)N)N) rqrrrsrrrr3rrrr*r*r*r+rs   rc@seZdZddZdS) Debian8PleskcOstdSrrrr*r*r+rszDebian8Plesk.__init__Nrr*r*r*r+rsrc@seZdZddZdS) Debian10PleskcOstdSrrrr*r*r+rszDebian10Plesk.__init__Nrr*r*r*r+rsrc@seZdZddZdS) Debian11PleskcOstdSrrrr*r*r+rszDebian11Plesk.__init__Nrr*r*r*r+rsrc@seZdZddZdS) Debian12PleskcOstdSrrrr*r*r+rszDebian12Plesk.__init__Nrr*r*r*r+rsrc@seZdZegZdS)Ubuntu1604PleskNrqrrrsrr!r*r*r*r+rsrc@seZdZegZdS)Ubuntu2004PleskNrr*r*r*r+rsrc@seZdZegZdS)Ubuntu2204PleskNrr*r*r*r+rsrc@seZdZegZdS)Ubuntu2404PleskNrr*r*r*r+rsr)=typingrrr]r[r"r|rGrenumrZ fileinputrZcustomer_local_opsrrrZ)customer_local_ops.operating_system.linuxr r r r r rrrrrrrrrrZ&customer_local_ops.control_panel.pleskrZcustomer_local_ops.util.executerrrZcustomer_local_ops.util.retryr getLoggerrqr%rrrrrrrrrrrrrrrrrr*r*r*r+sB  D   251__pycache__/plesk.cpython-38.pyc000064400000024571147205401360012535 0ustar00U afo5@sddlZddlZddlmZmZmZmZddlmZm Z m Z m Z ddl m Z mZeeZdZdZGdddZGd d d e ZdS) N)AnyTupleDictList) NydusResultOps ResourceTypeOpType)retryRetry c@seZdZdZdZdZdZdZdddd d d gZe j eed ed ed eiie j eed ed ed eiie j eed ed ed eiie jeed ed ed eiiiZedddZdeeeedddZdS)OSPleskzVMixin class representing common functionality and constants for Plesk between all OSes enable_pleskZrun_disable_session_ip_checkZrun_hide_internal_ipZ setup_cmdz&{init_conf_cmd} {init_conf_setup_flag}z -name adminz-passwd {password}z-license_agreed truez-email noreply@secureserver.netz-admin_info_not_required trueFT )returncCstdS)zFind the installation directory for Plesk :raises RuntimeError: If a Plesk path cannot be found :return: Full path to Plesk installation N)NotImplementedError)selfrW/opt/nydus/tmp/pip-target-53d1vnqk/lib/python/customer_local_ops/control_panel/plesk.py get_plesk_dir@szOSPlesk.get_plesk_dirN)filenamedirsrcCs:|dkrdg}|}|dk r$||tjj|f|S)aGet the path for a Plesk file/command/directory :param filename: Name of file or executable. If None, only directory is returned. :param dirs: List of directories under Plesk directory, in order of depth. These are joined to make the path. :raises RuntimeError: If a Plesk path cannot be found :return: Full path to Plesk file/command/directory Nbin)appendospathjoinr)rrrargsrrrget_path_pleskIs  zOSPlesk.get_path_plesk)NN)__name__ __module__ __qualname____doc__ZOP_ENABLE_PLESKZRUN_DISABLE_SESSION_IP_CHECKZRUN_HIDE_INTERNAL_IPZ SETUP_CMDZ"PLESK_SETUP_CMD_FORMAT_STRING_LISTrZ OPENSTACKrZOVHZ VIRTUOZZO_VMZOPENSTACK_HOSTINGCLOUDZ PLESK_OPS_RESOURCE_ATTRIBUTE_MAPstrrrrrrrrrsn rc@seZdZejZdZdZeeedde e e e e e fe e e e e ffdddZdZdZeeedde e e e e e e e e fe e e e e ffd d d ZdZdZeeedde e e e e fe d d dZdZdZeeedde e e e e fe e e e e ffd ddZdZdZeeedde e e e e fe d ddZdZdZeeeddde e e fe e e e e e e ffdddZe e e fe e e e e e ffdddZdZdZ ee edde e e fe e e e fe e e e e ffdddZ!dZ"dZ#ee#e"dde e e fe e e e fe e e e e ffdd d!Z$dZ%dZ&ee&e%dde e e e e e fe'd"d#d$Z(dS)%Pleski,Nintermediate_result)os_opactivation_keyrr(rcGs2tdd}||}|j||d}|||S)adRun license utility on local vm with activation key :param os_op: Operating system customer_local_ops class of target server :param activation_key: Key value passed back from license plesk op on hfs executor :param intermediate_result: Dict containing retry-related metadata :return: tuple with success, error data z license plesk license_pleskr')LOGinfo get_os_opr+build_result_from_other_result)rr)r*r(rop_namecp_os_op_instance os_resultrrrr+_s  zPlesk.license_pleski)r)vm_ip vm_resource plesk_user plesk_passrr(rc Gs8tdd}||} | j|||||d} || |S)aEnable plesk on local server :param os_op: Operating system customer_local_ops class of target server :param vm_ip: External IP address of the server :param vm_resource: The resource name for the third-party hosting provider :param plesk_user: User name to be used on Plesk instance :param plesk_pass: Password to be used on Plesk instance :param intermediate_result: Dict containing retry-related metadata :return: tuple with success, error data z enable pleskrr')r,r-r.rr/) rr)r3r4r5r6r(rr0r1r2rrrrrs   zPlesk.enable_plesk)r)rr(rcGs td||}|j|dS)zRetrieve a list of Plesk sites :param os_op: Operating system customer_local_ops class of target server :param intermediate_result: Dict containing retry-related metadata :return: list of sites or tuple with error data z get site listr')r,r-r.Zsite_list_pleskrr)r(rr1rrr site_lists  zPlesk.site_listicGs@tdt|tdd}||}|j|d}|||S)z Install Plesk on a server :param os_op: Operating system customer_local_ops class of target server :param intermediate_result: Dict containing retry-related metadata :return: tuple with success, error data zinstall plesk on '%s'z server prep server_prepr')r,r-r$r.Zserver_prep_pleskr/)rr)r(rr0r1r2rrrr9s    zPlesk.server_prepcGs td||}|j|dS)zGet Plesk SSO URL :param os_op: Operating system customer_local_ops class of target server :param intermediate_result: Dict containing retry-related metadata :return: SSO URL string or tuple with error data z get clientr')r,r-r.Zget_client_pleskr7rrr get_clients  zPlesk.get_clienti)intervaltimeout)payloadrr(rc Gs|d}d}tdtdt||z||}Wn<tk rr}zd|dt||fWYSd}~XYnX|j|||d}t|tr|S| |} td | j | j | j | ||S) aConfigure mail transfer agent on the Plesk server :param payload: Dict containing op params :param intermediate_result: Dict containing result data and meta data for retries :return: tuple with success, retry or error data r) configure_mtazPlesk.configure_mta startz4Plesk.configure_mta deferring to OS operation: %s.%sFNr'z3Plesk.configure_mta cp_os_op result - %s - %s - %s)r,r-r$r.AttributeErrorZbuild_result_dictZdo_configure_mta isinstancer get_result_datasuccessoutserrsr/) rr>r(rr)r0r1exr2datarrrr?s  ,  zPlesk.configure_mta)r>rrcGs2d}|d}tdt||||}||S)z Change password on the Plesk server :param payload: Dict containing op params :return: tuple with success or error data change_passwordr)zAPlesk.change_password is a NOOP, deferring to OS operation: %s.%s)r,r-r$r.rI)rr>rr0r)Zos_op_instancerrrrIs  zPlesk.change_passwordcGs4|d}d}||}|j|d|d}|||S)z Change admin password on the Plesk server :param payload: Dict containing op params :param intermediate_result: Dict containing retry-related metadata :return: tuple with success or error data r)change_admin_passwordZplesk_admin_passr')r.Zchange_admin_password_pleskr/)rr>r(rr)r0r1r2rrrrJs   zPlesk.change_admin_passwordxc Gstd|d}d}|d}||}tdt||||}||} td| j| j| j | jsv| ||S|j ||d} | | |S)z Change hostname on the Plesk server :param payload: Dict containing op params :param intermediate_result: Dict containing retry-related metadata :return: tuple with success or error data zPlesk.change_hostname startr)change_hostnamehostnamez6Plesk.change_hostname deferring to OS operation: %s.%sz2Plesk.change_hostname os_op_result - %s - %s - %sr') r,debugr.r-r$rLrCrDrErFr/Zchange_hostname_plesk) rr>r(rr)r0rMr1r2rHresultrrrrLs     zPlesk.change_hostname)r)addressrr(rcGs||j||dS)aSet Plesk's outgoing e-mail IP address. See General #6 for more information: https://docs.plesk.com/en-US/obsidian/administrator-guide/mail/configuring-serverwide-mail-settings.59430/ :param os_op: Operating system customer_local_ops class of target server :param address: IP address from which to send e-mail :param intermediate_result: Dict containing retry-related metadata :return: Nydus operation result r')r.set_outgoing_email_ip)rr)rPr(rrrrrQs zPlesk.set_outgoing_email_ip))r r!r"r Z CONTROL_PANELZop_typeZRETRY_LICENSE_PLESK_TIMEOUTZRETRY_LICENSE_PLESK_INTERVALr r$rrrboolr+ZRETRY_ENABLE_PLESK_TIMEOUTZRETRY_ENABLE_PLESK_INTERVALrZRETRY_SITE_LIST_TIMEOUTZRETRY_SITE_LIST_INTERVALr8ZRETRY_SERVER_PREP_TIMEOUTZRETRY_SERVER_PREP_INTERVALr9ZRETRY_GET_CLIENT_TIMEOUTZRETRY_GET_CLIENT_INTERVALr:ZRETRY_CONFIGURE_MTA_TIMEOUTZ"RETRY_CONFIGURE_MTA_RETRY_INTERVALr?rIZ#RETRY_CHANGE_ADMIN_PASSWORD_TIMEOUTZ$RETRY_CHANGE_ADMIN_PASSWORD_INTERVALrJZRETRY_CHANGE_HOSTNAME_TIMEOUTZRETRY_CHANGE_HOSTNAME_INTERVALrLZRETRY_SET_OUTGOING_MAIL_TIMEOUTZ RETRY_SET_OUTGOING_MAIL_INTERVALrrQrrrrr%Ys      $  *     r%)loggingrtypingrrrrZcustomer_local_opsrrrr Zcustomer_local_ops.util.retryr r getLoggerr r,Z%CHECK_FOR_DLL_CONFLICT_RETRY_INTERVALZ$CHECK_FOR_DLL_CONFLICT_RETRY_TIMEOUTrr%rrrrs C__pycache__/windows_plesk.cpython-38.pyc000064400000026614147205401360014307 0ustar00U af6 @sTddlmZddlmZmZmZmZmZddlZddl Z ddl Z ddl Z ddl m Z mZmZddlmZmZmZmZddlmZddlmZddlmZmZmZmZmZdd lm Z e!e"Z#d Z$d Z%d Z&d Z'ee(ee(eee(ee(ee(fffZ)e(e*dddZ+GdddeeZ,Gdddee,Z-Gdddee,Z.Gdddee,Z/dS)) list2cmdline)AnyDictTupleUnionListN)OpType ResourceType NydusResult)Windows Windows2016 Windows2019 Windows2022)SCRIPT_BASEPATH)OSPlesk) runCommandrun_powershell_filerun_uapi_commandrun_multiple_uapi_commandsstart_powershell_file)RetryzC:\Program Files (x86)\Plesk\z'C:\Program Files (x86)\Parallels\Plesk\ z*C:\Windows\TEMP\plesk-fix-dll-conflict.log)textreturncCs&d|k}|rtdn td|S)z Check to see of the specified text contains an indication of the Python/PHP DLL conflict :param text: The text to be checked :return: True if the conflict is detected; otherwise, False z)'vcruntime140.dll' 14.0 is not compatiblezDLL conflict detectedzNo DLL conflict detected)LOGdebug)rZconflict_detectedr_/opt/nydus/tmp/pip-target-53d1vnqk/lib/python/customer_local_ops/control_panel/windows_plesk.pyhas_dll_version_conflicts   rc speZdZdZejZdZdddgZe dddZ dd e e e e e fe d d d Zdd e e e e e e e e fe d ddZdd e e e e fe dddZdd e e e e fe dddZdd e e e e fe dddZdd e e e e e fe dddZdd e e e e e fe dddZd#ee e e fe e e fe dddZd$ee e e e e e fe d fd!d" ZZS)% WindowsPleskz Plesk Customer Local Ops for the Windows OS. All function names should contain 'plesk' so as not to override the OS ops NzWThe Plesk administrator password cannot be changed until the server cloning is finishedzJNo connection could be made because the target machine actively refused itzCould not resolve host)rcCs|jdkrptdr"td|_nNtjtr6t|_n:tjtrJt|_n&tjddd}|rpt|t rp|d|_|jdk rt d|j|jSt ddS) zFind the installation directory for plesk :raises RuntimeError: If a plesk path cannot be found :return: full path to plesk installation N plesk_dirz C:\Program Files (x86)\**\Plesk\T) recursiverzget_plesk_dir: %szplesk path could not be found) r osgetenvpathexists PLESK_DIR_17 PLESK_DIR_12glob isinstancelistrinfo RuntimeError)selfZ pleskpathrrr get_plesk_dir9s      zWindowsPlesk.get_plesk_dirintermediate_result)activation_keyargsr0rc Gsdtdt|d}d}||}td|dj||d}t|dddd \}} } ||| | ||S) aRun license utility on local vm with activation key :param activation_key: Key value passed back from license plesk op on hfs executor :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data zinstall plesk license on '%s' license_pleskz license.exezfull command: %sz+"{full_command}" --install {activation_key}) full_commandr1zset Plesk LicenseTZ use_shell)rr+strget_path_pleskformatr_result_handler) r-r1r0r2op_namecommandr4cmd exit_codeoutserrsrrrr3Ss   zWindowsPlesk.license_plesk)vm_ip vm_resource plesk_user plesk_passr2r0rc Gs.td||j}t||g}|jt||} |d} d| } t | d|dd\} } }| | | |||}t |t r~|S|dsd}nd }d d j|d d fd| |j jd| |||dfddj|ddfddj|dddgdfdd|dfg}t|||d\} } }| | | |||S)aEnable plesk on local server :param vm_ip: External IP address of the server :param vm_resource: The resource name for the third-party hosting provider :param plesk_user: User name to be used on Plesk instance :param plesk_pass: Password to be used on Plesk instance :param intermediate_result: Dict containing metadata for retries :raises DecryptError: if there is a problem with decrypting the password :return: tuple with success, error data zenable plesk for VM IP %sz init_conf.exez"{}" --check-configuredzCheck Plesk configuredTr5rz--initz--updatezset minimum password strengthz0"{server_pref}" -u -min_password_strength mediumserver_pref.exe)Z server_prefz setup Pleskz"{}") init_conf_cmdinit_conf_setup_flagrBpasswordz set Poweruserz"{poweruser_cmd}" --onz poweruser.exe)Z poweruser_cmdreconfiguratorz*"{reconfigurator}" /check=Services /no-guizreconfigurator.exeadminbin)rHzRepair sslcertsz"{}" repair web -sslcertsz plesk.exe) omit_string)rr+ZOP_ENABLE_PLESKrdecryptZ PLESK_OPS_RESOURCE_ATTRIBUTE_MAPr r7r8rr9r)rZ SETUP_CMDr)r-r@rArBrCr0r2r:Z password_argZops_maprEZcheck_configured_cmdr=r>r?resrFZ enable_cmdsrrr enable_pleskgsH      zWindowsPlesk.enable_plesk)r2r0rcGsntdd}t|g}ttdd|d|\}}}|dkrR||||||St||rjt |SdS)z Retrieve a list of Plesk sites :param intermediate_result: Dict containing metadata for retries :return: list of sites or tuple with error data z get site listsite_list_plesk powershellzplesk_site_list.ps1Fr) rr+r6r.rrr9loggingjsonloads)r-r0r2r:Z plesk_dir_argr=r>r?rrrrOs    zWindowsPlesk.site_list_pleskcGs>tdt|d}ttddd\}}}||||||S)z Install Plesk on a server :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data zinstall plesk on '%s'server_prep_pleskrPzplesk_server_prep.ps1z Prep Plesk)rr+r6rrr9)r-r0r2r:r=r>r?rrrrUs  zWindowsPlesk.server_prep_pleskc Gs\d}tddddgd\}}}|dkr6||||||S|d}t|d}||d S) z Get Plesk SSO URL :param intermediate_result: Dict containing metadata for retries :return: SSO URL string or tuple with error data get_client_pleskZpleskrJrIz--get-login-linkz get sso linkr zutf-8)rr9splitr6Zencryptdecode) r-r0r2r:r=r>r?linksZsso_urlrrrrVs  zWindowsPlesk.get_client_plesk)hostnamer2r0rc Gs@d}dj|d|d}t|d|dd\}}}||||||S)z Change hostname on Plesk :param hostname: New name to be assigned to the host :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data change_hostname_pleskz,"{plesk_path}" --update -hostname {hostname}rD)Z plesk_pathr[zset Plesk hostnameTr5)r8r7rr9) r-r[r0r2r:Zrcmdr=r>r?rrrr\s  z"WindowsPlesk.change_hostname_plesk)plesk_admin_passr2r0rc GsZd}||}d|i}|dddg}|dd|g}t|d|f|\} } } || | | ||S) z Change admin password on Plesk :param plesk_admin_pass: Encrypted password for Plesk admin user :param intermediate_result: Dict containing metadata for retries :return: tuple with success, error data change_admin_password_pleskrKNrJrIz--set-admin-passwordz-passwdzset Plesk admin password)rLr7rr9) r-r]r0r2r:rGZcommand_kwargsZ plesk_binr;r=r>r?rrrr^s   z(WindowsPlesk.change_admin_password_plesk)r=resultr0rcCs|dkrH|dk r@|ddr@zttWntk r>YnXd|fSt|ddrtdtt d d \}}}|dkr|dkri}d|d<|dkrt |Sd|fSd|fS) a Check to see if the output of the previous command indicates that a DLL version conflict exists and, if so attempt remediation. :param exit_code: The exit code from the previous command :param result: The result dictionary from the previous command :param intermediate_result: Dict containing metadata for retries :return: A 2-tuple if no remediation is necessary, otherwise a 3-tuple where the last element is a retry interval in seconds rNZdelete_log_fileFTr?rQz.DLL conflict detected; attempting to remediaterPzplesk_fix_dll_conflict.ps1) getr"unlink&PLESK_FIX_DLL_CONFLICT_SCRIPT_LOG_FILE Exceptionrrerrorrrr)r-r=r_r0code_rrr_check_for_dll_conflicts  z$WindowsPlesk._check_for_dll_conflict)r=r>r?r:r0rc sX||||||d\}}|sP|j|||d}t|tr<|St|||||S||fS)aTake the result from a run command and check for retryable errors. If found, return a Retry. If not, return a Nydus result tuple. :param exit_code: The exit code from the executed command :param outs: The stdout output from the executed command :param errs: The stderr output from the executed command :param op_name: The name of the op :param intermediate_result: Dict containing metadata for retries :return: A Nydus result tuple, or Retry z succeededr/)Zbuild_result_from_cmd_outputrgr)rsuperr9) r-r=r>r?r:r0successr_Zdll_res __class__rrr9s  zWindowsPlesk._result_handler)N)N)__name__ __module__ __qualname____doc__rZCONTROL_PANEL_OPERATING_SYSTEMZop_typer ZRETRYABLE_ERRSr6r.rrr r3rNrOrUrVr\r^intrgr9 __classcell__rrrjrr.sT     1          rc@s eZdZdS)Windows2016PleskNrlrmrnrrrrrrsrrc@s eZdZdS)Windows2019PleskNrsrrrrrtsrtc@s eZdZdS)Windows2022PleskNrsrrrrru"sru)0 subprocessrtypingrrrrrrRr(rSr"Zcustomer_local_opsrr r Z+customer_local_ops.operating_system.windowsr r r rZ customer_local_ops.control_panelrZ&customer_local_ops.control_panel.pleskrZcustomer_local_ops.util.executerrrrrZcustomer_local_ops.util.retryr getLoggerrlrr&r'Z#DLL_CONFLICT_COMMAND_RETRY_INTERVALrbr6ZCmdsTypeboolrrrrrtrurrrrs.     *m