File manager - Edit - /usr/local/CyberCP/plogical/DockerSites.py
Back
#!/usr/local/CyberCP/bin/python import json import os import sys import time from random import randint import socket import shutil import docker sys.path.append('/usr/local/CyberCP') try: import django except: pass try: from plogical import randomPassword from plogical.acl import ACLManager from dockerManager.dockerInstall import DockerInstall except: pass from plogical.processUtilities import ProcessUtilities from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging import argparse import threading as multi class DockerDeploymentError(Exception): def __init__(self, message, error_code=None, recovery_possible=True): self.message = message self.error_code = error_code self.recovery_possible = recovery_possible super().__init__(self.message) class Docker_Sites(multi.Thread): Wordpress = 1 Joomla = 2 # Error codes ERROR_DOCKER_NOT_INSTALLED = 'DOCKER_NOT_INSTALLED' ERROR_PORT_IN_USE = 'PORT_IN_USE' ERROR_CONTAINER_FAILED = 'CONTAINER_FAILED' ERROR_NETWORK_FAILED = 'NETWORK_FAILED' ERROR_VOLUME_FAILED = 'VOLUME_FAILED' ERROR_DB_FAILED = 'DB_FAILED' def __init__(self, function_run, data): multi.Thread.__init__(self) self.function_run = function_run self.data = data try: self.JobID = self.data['JobID'] ##JOBID will be file path where status is being written except: pass try: ### set docker name for listing/deleting etc if ProcessUtilities.decideDistro() == ProcessUtilities.centos or ProcessUtilities.decideDistro() == ProcessUtilities.cent8: self.DockerAppName = f"{self.data['name'].replace(' ', '')}-{self.data['name'].replace(' ', '-')}" else: self.DockerAppName = f"{self.data['name'].replace(' ', '')}_{self.data['name'].replace(' ', '-')}" except: pass command = 'cat /etc/csf/csf.conf' result = ProcessUtilities.outputExecutioner(command) if result.find('SECTION:Initial Settings') > -1: from plogical.csf import CSF from plogical.virtualHostUtilities import virtualHostUtilities currentSettings = CSF.fetchCSFSettings() tcpIN = currentSettings['tcpIN'] if os.path.exists(ProcessUtilities.debugPath): logging.writeToFile(f'TCPIN docker: {tcpIN}') if tcpIN.find('8088') == -1: ports = f'{tcpIN},8088' portsPath = '/home/cyberpanel/' + str(randint(1000, 9999)) if os.path.exists(portsPath): os.remove(portsPath) writeToFile = open(portsPath, 'w') writeToFile.write(ports) writeToFile.close() execPath = "sudo /usr/local/CyberCP/bin/python " + virtualHostUtilities.cyberPanel + "/plogical/csf.py" execPath = execPath + f" modifyPorts --protocol TCP_IN --ports " + portsPath ProcessUtilities.executioner(execPath) tcpOUT = currentSettings['tcpOUT'] if tcpOUT.find('8088') == -1: ports = f'{tcpOUT},8088' portsPath = '/home/cyberpanel/' + str(randint(1000, 9999)) if os.path.exists(portsPath): os.remove(portsPath) writeToFile = open(portsPath, 'w') writeToFile.write(ports) writeToFile.close() execPath = "sudo /usr/local/CyberCP/bin/python " + virtualHostUtilities.cyberPanel + "/plogical/csf.py" execPath = execPath + f" modifyPorts --protocol TCP_OUT --ports " + portsPath ProcessUtilities.executioner(execPath) def run(self): try: if self.function_run == 'DeployWPContainer': self.DeployWPContainer() elif self.function_run == 'SubmitDockersiteCreation': self.SubmitDockersiteCreation() elif self.function_run == 'DeployN8NContainer': self.DeployN8NContainer() except BaseException as msg: logging.writeToFile(str(msg) + ' [Docker_Sites.run]') def InstallDocker(self): if ProcessUtilities.decideDistro() == ProcessUtilities.centos or ProcessUtilities.decideDistro() == ProcessUtilities.cent8: command = 'dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo' ReturnCode = ProcessUtilities.executioner(command) if ReturnCode: pass else: return 0, ReturnCode command = 'dnf install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y' ReturnCode = ProcessUtilities.executioner(command) if ReturnCode: pass else: return 0, ReturnCode command = 'systemctl enable docker' ReturnCode = ProcessUtilities.executioner(command) if ReturnCode: pass else: return 0, ReturnCode command = 'systemctl start docker' ReturnCode = ProcessUtilities.executioner(command) if ReturnCode: pass else: return 0, ReturnCode command = 'curl -L "https://github.com/docker/compose/releases/download/v2.23.2/docker-compose-linux-x86_64" -o /usr/bin/docker-compose' ReturnCode = ProcessUtilities.executioner(command, 'root', True) if ReturnCode: pass else: return 0, ReturnCode command = 'chmod +x /usr/bin/docker-compose' ReturnCode = ProcessUtilities.executioner(command, 'root', True) if ReturnCode: return 1, None else: return 0, ReturnCode else: # Add Docker's official GPG key command = 'curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg' ReturnCode = ProcessUtilities.executioner(command, 'root', True) if not ReturnCode: return 0, ReturnCode # Add Docker repository command = 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null' ReturnCode = ProcessUtilities.executioner(command, 'root', True) if not ReturnCode: return 0, ReturnCode # Update package index command = 'apt-get update' ReturnCode = ProcessUtilities.executioner(command) if not ReturnCode: return 0, ReturnCode # Install Docker packages command = 'apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin' ReturnCode = ProcessUtilities.executioner(command) if not ReturnCode: return 0, ReturnCode # Enable and start Docker service command = 'systemctl enable docker' ReturnCode = ProcessUtilities.executioner(command) if not ReturnCode: return 0, ReturnCode command = 'systemctl start docker' ReturnCode = ProcessUtilities.executioner(command) if not ReturnCode: return 0, ReturnCode # Install Docker Compose command = 'curl -L "https://github.com/docker/compose/releases/download/v2.23.2/docker-compose-linux-$(uname -m)" -o /usr/local/bin/docker-compose' ReturnCode = ProcessUtilities.executioner(command, 'root', True) if not ReturnCode: return 0, ReturnCode command = 'chmod +x /usr/local/bin/docker-compose' ReturnCode = ProcessUtilities.executioner(command, 'root', True) if not ReturnCode: return 0, ReturnCode return 1, None @staticmethod def SetupProxy(port): import xml.etree.ElementTree as ET if ProcessUtilities.decideServer() == ProcessUtilities.OLS: ConfPath = '/usr/local/lsws/conf/httpd_config.conf' data = open(ConfPath, 'r').read() StringCheck = f"127.0.0.1:{port}" if data.find(StringCheck) == -1: ProxyContent = f""" extprocessor docker{port} {{ type proxy address 127.0.0.1:{port} maxConns 100 pcKeepAliveTimeout 60 initTimeout 60 retryTimeout 0 respBuffer 0 }} """ WriteToFile = open(ConfPath, 'a') WriteToFile.write(ProxyContent) WriteToFile.close() else: ConfPath = '/usr/local/lsws/conf/httpd_config.xml' data = open(ConfPath, 'r').read() # Parse the XML root = ET.fromstring(data) # Find the <extProcessorList> node ext_processor_list = root.find('extProcessorList') # Create the new <extProcessor> node new_ext_processor = ET.Element('extProcessor') # Add child elements to the new <extProcessor> ET.SubElement(new_ext_processor, 'type').text = 'proxy' ET.SubElement(new_ext_processor, 'name').text = f'docker{port}' ET.SubElement(new_ext_processor, 'address').text = f'127.0.0.1:{port}' ET.SubElement(new_ext_processor, 'maxConns').text = '35' ET.SubElement(new_ext_processor, 'pcKeepAliveTimeout').text = '60' ET.SubElement(new_ext_processor, 'initTimeout').text = '60' ET.SubElement(new_ext_processor, 'retryTimeout').text = '60' ET.SubElement(new_ext_processor, 'respBuffer').text = '0' # Append the new <extProcessor> to the <extProcessorList> ext_processor_list.append(new_ext_processor) # Write the updated XML content to a new file or print it out tree = ET.ElementTree(root) tree.write(ConfPath, encoding='UTF-8', xml_declaration=True) # Optionally, print the updated XML ET.dump(root) @staticmethod def SetupHTAccess(port, htaccess): ### Update htaccess StringCheck = f'docker{port}' try: Content = open(htaccess, 'r').read() except: Content = '' print(f'value of content {Content}') if Content.find(StringCheck) == -1: HTAccessContent = f''' RewriteEngine On REWRITERULE ^(.*)$ HTTP://docker{port}/$1 [P] ''' WriteToFile = open(htaccess, 'a') WriteToFile.write(HTAccessContent) WriteToFile.close() # Takes # ComposePath, MySQLPath, MySQLRootPass, MySQLDBName, MySQLDBNUser, MySQLPassword, CPUsMySQL, MemoryMySQL, # port, SitePath, CPUsSite, MemorySite, ComposePath, SiteName # finalURL, blogTitle, adminUser, adminPassword, adminEmail, htaccessPath, externalApp def DeployWPContainer(self): try: logging.statusWriter(self.JobID, 'Checking if Docker is installed..,0') command = 'docker --help' result = ProcessUtilities.outputExecutioner(command) if os.path.exists(ProcessUtilities.debugPath): logging.writeToFile(f'return code of docker install {result}') if result.find("not found") > -1: if os.path.exists(ProcessUtilities.debugPath): logging.writeToFile(f'About to run docker install function...') execPath = "/usr/local/CyberCP/bin/python /usr/local/CyberCP/dockerManager/dockerInstall.py" ProcessUtilities.executioner(execPath) logging.statusWriter(self.JobID, 'Docker is ready to use..,10') self.data['ServiceName'] = self.data["SiteName"].replace(' ', '-') WPSite = f''' version: '3.8' services: '{self.data['ServiceName']}': user: root image: cyberpanel/openlitespeed:latest ports: - "{self.data['port']}:8088" # - "443:443" environment: DB_NAME: "{self.data['MySQLDBName']}" DB_USER: "{self.data['MySQLDBNUser']}" DB_PASSWORD: "{self.data['MySQLPassword']}" WP_ADMIN_EMAIL: "{self.data['adminEmail']}" WP_ADMIN_USER: "{self.data['adminUser']}" WP_ADMIN_PASSWORD: "{self.data['adminPassword']}" WP_URL: {self.data['finalURL']} DB_Host: '{self.data['ServiceName']}-db:3306' SITE_NAME: '{self.data['SiteName']}' volumes: # - "/home/docker/{self.data['finalURL']}:/usr/local/lsws/Example/html" - "/home/docker/{self.data['finalURL']}/data:/usr/local/lsws/Example/html" depends_on: - '{self.data['ServiceName']}-db' deploy: resources: limits: cpus: '{self.data['CPUsSite']}' # Use 50% of one CPU core memory: {self.data['MemorySite']}M # Limit memory to 512 megabytes '{self.data['ServiceName']}-db': image: mariadb restart: always environment: # ALLOW_EMPTY_PASSWORD=no MYSQL_DATABASE: '{self.data['MySQLDBName']}' MYSQL_USER: '{self.data['MySQLDBNUser']}' MYSQL_PASSWORD: '{self.data['MySQLPassword']}' MYSQL_ROOT_PASSWORD: '{self.data['MySQLPassword']}' volumes: - "/home/docker/{self.data['finalURL']}/db:/var/lib/mysql" deploy: resources: limits: cpus: '{self.data['CPUsMySQL']}' # Use 50% of one CPU core memory: {self.data['MemoryMySQL']}M # Limit memory to 512 megabytes ''' ### WriteConfig to compose-file command = f"mkdir -p /home/docker/{self.data['finalURL']}" result, message = ProcessUtilities.outputExecutioner(command, None, None, None, 1) if result == 0: logging.statusWriter(self.JobID, f'Error {str(message)} . [404]') return 0 TempCompose = f'/home/cyberpanel/{self.data["finalURL"]}-docker-compose.yml' WriteToFile = open(TempCompose, 'w') WriteToFile.write(WPSite) WriteToFile.close() command = f"mv {TempCompose} {self.data['ComposePath']}" result, message = ProcessUtilities.outputExecutioner(command, None, None, None, 1) if result == 0: logging.statusWriter(self.JobID, f'Error {str(message)} . [404]') return 0 command = f"chmod 600 {self.data['ComposePath']} && chown root:root {self.data['ComposePath']}" ProcessUtilities.executioner(command, 'root', True) #### if ProcessUtilities.decideDistro() == ProcessUtilities.cent8 or ProcessUtilities.decideDistro() == ProcessUtilities.centos: dockerCommand = 'docker compose' else: dockerCommand = 'docker-compose' command = f"{dockerCommand} -f {self.data['ComposePath']} -p '{self.data['SiteName']}' up -d" result, message = ProcessUtilities.outputExecutioner(command, None, None, None, 1) if os.path.exists(ProcessUtilities.debugPath): logging.writeToFile(message) if result == 0: logging.statusWriter(self.JobID, f'Error {str(message)} . [404]') return 0 logging.statusWriter(self.JobID, 'Bringing containers online..,50') time.sleep(25) ### checking if everything ran properly passdata = {} passdata["JobID"] = None passdata['name'] = self.data['ServiceName'] da = Docker_Sites(None, passdata) retdata, containers = da.ListContainers() containers = json.loads(containers) if os.path.exists(ProcessUtilities.debugPath): logging.writeToFile(str(containers)) ### it means less then two containers which means something went wrong if len(containers) < 2: logging.writeToFile(f'Unkonwn error, containers not running. [DeployWPContainer]') logging.statusWriter(self.JobID, f'Unkonwn error, containers not running. [DeployWPContainer]') return 0 ### Set up Proxy execPath = "/usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/DockerSites.py" execPath = execPath + f" SetupProxy --port {self.data['port']}" ProcessUtilities.executioner(execPath) ### Set up ht access execPath = "/usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/DockerSites.py" execPath = execPath + f" SetupHTAccess --port {self.data['port']} --htaccess {self.data['htaccessPath']}" ProcessUtilities.executioner(execPath, self.data['externalApp']) if ProcessUtilities.decideDistro() == ProcessUtilities.centos or ProcessUtilities.decideDistro() == ProcessUtilities.cent8: group = 'nobody' else: group = 'nogroup' command = f"chown -R nobody:{group} /home/docker/{self.data['finalURL']}/data" ProcessUtilities.executioner(command) ### just restart ls for htaccess from plogical.installUtilities import installUtilities installUtilities.reStartLiteSpeedSocket() logging.statusWriter(self.JobID, 'Completed. [200]') # command = f"docker-compose -f {self.data['ComposePath']} ps -q wordpress" # stdout = ProcessUtilities.outputExecutioner(command) # # self.ContainerID = stdout.rstrip('\n') # command = f'docker-compose -f {self.data["ComposePath"]} exec {self.ContainerID} curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar' # result = ProcessUtilities.outputExecutioner(command) # # if os.path.exists(ProcessUtilities.debugPath): # logging.writeToFile(result) # # command = f"docker-compose -f {self.data['ComposePath']} exec {self.ContainerID} chmod + wp-cli.phar" # result = ProcessUtilities.outputExecutioner(command) # # if os.path.exists(ProcessUtilities.debugPath): # logging.writeToFile(result) # # command = f"docker-compose -f {self.data['ComposePath']} exec {self.ContainerID} mv wp-cli.phar /bin/wp" # result = ProcessUtilities.outputExecutioner(command) # # if os.path.exists(ProcessUtilities.debugPath): # logging.writeToFile(result) # command = f'docker-compose -f {self.data["ComposePath"]} exec {self.ContainerID} wp core install --url="http://{self.data["finalURL"]}" --title="{self.data["blogTitle"]}" --admin_user="{self.data["adminUser"]}" --admin_password="{self.data["adminPassword"]}" --admin_email="{self.data["adminEmail"]}" --path=. --allow-root' # result = ProcessUtilities.outputExecutioner(command) # # if os.path.exists(ProcessUtilities.debugPath): # logging.writeToFile(result) except BaseException as msg: logging.writeToFile(f'{str(msg)}. [DeployWPContainer]') logging.statusWriter(self.JobID, f'Error {str(msg)} . [404]') print(str(msg)) pass def SubmitDockersiteCreation(self): try: from websiteFunctions.models import DockerSites, Websites from websiteFunctions.website import WebsiteManager tempStatusPath = self.data['JobID'] statusFile = open(tempStatusPath, 'w') statusFile.writelines('Creating Website...,10') statusFile.close() Domain = self.data['Domain'] WPemal = self.data['WPemal'] Owner = self.data['Owner'] userID = self.data['userID'] MysqlCPU = self.data['MysqlCPU'] MYsqlRam = self.data['MYsqlRam'] SiteCPU = self.data['SiteCPU'] SiteRam = self.data['SiteRam'] sitename = self.data['sitename'] WPusername = self.data['WPusername'] WPpasswd = self.data['WPpasswd'] externalApp = self.data['externalApp'] currentTemp = tempStatusPath DataToPass = {} DataToPass['tempStatusPath'] = tempStatusPath DataToPass['domainName'] = Domain DataToPass['adminEmail'] = WPemal DataToPass['phpSelection'] = "PHP 8.1" DataToPass['websiteOwner'] = Owner DataToPass['package'] = 'Default' DataToPass['ssl'] = 1 DataToPass['dkimCheck'] = 0 DataToPass['openBasedir'] = 0 DataToPass['mailDomain'] = 0 DataToPass['apacheBackend'] = 0 UserID = userID if Websites.objects.filter(domain=DataToPass['domainName']).count() == 0: try: website = Websites.objects.get(domain=DataToPass['domainName']) if website.phpSelection == 'PHP 7.3': website.phpSelection = 'PHP 8.0' website.save() if ACLManager.checkOwnership(website.domain, self.data['adminID'], self.data['currentACL']) == 0: statusFile = open(tempStatusPath, 'w') statusFile.writelines('You dont own this site.[404]') statusFile.close() except: ab = WebsiteManager() coreResult = ab.submitWebsiteCreation(UserID, DataToPass) coreResult1 = json.loads((coreResult).content) logging.writeToFile("Creating website result....%s" % coreResult1) reutrntempath = coreResult1['tempStatusPath'] while (1): lastLine = open(reutrntempath, 'r').read() logging.writeToFile("Error web creating lastline ....... %s" % lastLine) if lastLine.find('[200]') > -1: break elif lastLine.find('[404]') > -1: statusFile = open(currentTemp, 'w') statusFile.writelines('Failed to Create Website: error: %s. [404]' % lastLine) statusFile.close() return 0 else: statusFile = open(currentTemp, 'w') statusFile.writelines('Creating Website....,20') statusFile.close() time.sleep(2) statusFile = open(tempStatusPath, 'w') statusFile.writelines('Creating DockerSite....,30') statusFile.close() webobj = Websites.objects.get(domain=Domain) if webobj.dockersites_set.all().count() > 0: logging.statusWriter(self.JobID, f'Docker container already exists on this domain. [404]') return 0 dbname = randomPassword.generate_pass() dbpasswd = randomPassword.generate_pass() dbusername = randomPassword.generate_pass() MySQLRootPass = randomPassword.generate_pass() if DockerSites.objects.count() == 0: port = '11000' else: port = str(int(DockerSites.objects.last().port) + 1) f_data = { "JobID": tempStatusPath, "ComposePath": f"/home/docker/{Domain}/docker-compose.yml", "MySQLPath": f'/home/{Domain}/public_html/sqldocker', "MySQLRootPass": MySQLRootPass, "MySQLDBName": dbname, "MySQLDBNUser": dbusername, "MySQLPassword": dbpasswd, "CPUsMySQL": MysqlCPU, "MemoryMySQL": MYsqlRam, "port": port, "SitePath": f'/home/{Domain}/public_html/wpdocker', "CPUsSite": SiteCPU, "MemorySite": SiteRam, "SiteName": sitename, "finalURL": Domain, "blogTitle": sitename, "adminUser": WPusername, "adminPassword": WPpasswd, "adminEmail": WPemal, "htaccessPath": f'/home/{Domain}/public_html/.htaccess', "externalApp": webobj.externalApp, "docRoot": f"/home/{Domain}" } dockersiteobj = DockerSites( admin=webobj, ComposePath=f"/home/{Domain}/docker-compose.yml", SitePath=f'/home/{Domain}/public_html/wpdocker', MySQLPath=f'/home/{Domain}/public_html/sqldocker', SiteType=Docker_Sites.Wordpress, MySQLDBName=dbname, MySQLDBNUser=dbusername, CPUsMySQL=MysqlCPU, MemoryMySQL=MYsqlRam, port=port, CPUsSite=SiteCPU, MemorySite=SiteRam, SiteName=sitename, finalURL=Domain, blogTitle=sitename, adminUser=WPusername, adminEmail=WPemal ) dockersiteobj.save() if self.data['App'] == 'WordPress': background = Docker_Sites('DeployWPContainer', f_data) background.start() elif self.data['App'] == 'n8n': background = Docker_Sites('DeployN8NContainer', f_data) background.start() except BaseException as msg: logging.writeToFile("Error Submit Docker site Creation ....... %s" % str(msg)) return 0 def DeleteDockerApp(self): try: command = f'docker-compose -f /home/docker/{self.data["domain"]}/docker-compose.yml down' ProcessUtilities.executioner(command) command = f'rm -rf /home/docker/{self.data["domain"]}' ProcessUtilities.executioner(command) command = f'rm -f /home/{self.data["domain"]}/public_html/.htaccess' ProcessUtilities.executioner(command) ### forcefully delete containers # Create a Docker client client = docker.from_env() FilerValue = self.DockerAppName # Define the label to filter containers label_filter = {'name': FilerValue} # List containers matching the label filter containers = client.containers.list(filters=label_filter) logging.writeToFile(f'List of containers {str(containers)}') for container in containers: command = f'docker stop {container.short_id}' ProcessUtilities.executioner(command) command = f'docker rm {container.short_id}' ProcessUtilities.executioner(command) command = f"rm -rf /home/{self.data['domain']}/public_html/.htaccess'" ProcessUtilities.executioner(command) from plogical.installUtilities import installUtilities installUtilities.reStartLiteSpeed() except BaseException as msg: logging.writeToFile("Error Delete Docker APP ....... %s" % str(msg)) return 0 ## This function need site name which was passed while creating the app def ListContainers(self): try: # Create a Docker client client = docker.from_env() # Debug logging if os.path.exists(ProcessUtilities.debugPath): logging.writeToFile(f'DockerAppName: {self.DockerAppName}') # List all containers without filtering first all_containers = client.containers.list(all=True) if os.path.exists(ProcessUtilities.debugPath): logging.writeToFile(f'Total containers found: {len(all_containers)}') for container in all_containers: logging.writeToFile(f'Container name: {container.name}') # Now filter containers - handle both CentOS and Ubuntu naming containers = [] # Get both possible name formats if ProcessUtilities.decideDistro() == ProcessUtilities.centos or ProcessUtilities.decideDistro() == ProcessUtilities.cent8: search_name = self.DockerAppName # Already in hyphen format for CentOS else: # For Ubuntu, convert underscore to hyphen as containers use hyphens search_name = self.DockerAppName.replace('_', '-') if os.path.exists(ProcessUtilities.debugPath): logging.writeToFile(f'Searching for containers with name containing: {search_name}') for container in all_containers: if os.path.exists(ProcessUtilities.debugPath): logging.writeToFile(f'Checking container: {container.name} against filter: {search_name}') if search_name.lower() in container.name.lower(): containers.append(container) if os.path.exists(ProcessUtilities.debugPath): logging.writeToFile(f'Filtered containers count: {len(containers)}') json_data = "[" checker = 0 for container in containers: try: dic = { 'id': container.short_id, 'name': container.name, 'status': container.status, 'state': container.attrs.get('State', {}), 'health': container.attrs.get('State', {}).get('Health', {}).get('Status', 'unknown'), 'volumes': container.attrs['HostConfig']['Binds'] if 'HostConfig' in container.attrs else [], 'logs_50': container.logs(tail=50).decode('utf-8'), 'ports': container.attrs['HostConfig']['PortBindings'] if 'HostConfig' in container.attrs else {} } if checker == 0: json_data = json_data + json.dumps(dic) checker = 1 else: json_data = json_data + ',' + json.dumps(dic) except Exception as e: logging.writeToFile(f"Error processing container {container.name}: {str(e)}") continue json_data = json_data + ']' if os.path.exists(ProcessUtilities.debugPath): logging.writeToFile(f'Final JSON data: {json_data}') return 1, json_data except BaseException as msg: logging.writeToFile("List Container ....... %s" % str(msg)) return 0, str(msg) ### pass container id and number of lines to fetch from logs def ContainerLogs(self): try: # Create a Docker client client = docker.from_env() # Get the container by ID container = client.containers.get(self.data['containerID']) # Fetch last 'tail' logs for the container logs = container.logs(tail=self.data['numberOfLines']).decode('utf-8') return 1, logs except BaseException as msg: logging.writeToFile("List Container ....... %s" % str(msg)) return 0, str(msg) ### pass container id and number of lines to fetch from logs def ContainerInfo(self): try: # Create a Docker client client = docker.from_env() # Get the container by ID container = client.containers.get(self.data['containerID']) # Fetch container stats stats = container.stats(stream=False) dic = { 'id': container.short_id, 'name': container.name, 'status': container.status, 'volumes': container.attrs['HostConfig']['Binds'] if 'HostConfig' in container.attrs else [], 'logs_50': container.logs(tail=50).decode('utf-8'), 'ports': container.attrs['HostConfig']['PortBindings'] if 'HostConfig' in container.attrs else {}, 'memory': stats['memory_stats']['usage'], 'cpu' : stats['cpu_stats']['cpu_usage']['total_usage'] } return 1, dic except BaseException as msg: logging.writeToFile("List Container ....... %s" % str(msg)) return 0, str(msg) def RebuildApp(self): self.DeleteDockerApp() self.SubmitDockersiteCreation() def RestartContainer(self): try: # Create a Docker client client = docker.from_env() # Get the container by ID container = client.containers.get(self.data['containerID']) container.restart() return 1, None except BaseException as msg: logging.writeToFile("List Container ....... %s" % str(msg)) return 0, str(msg) def StopContainer(self): try: # Create a Docker client client = docker.from_env() # Get the container by ID container = client.containers.get(self.data['containerID']) container.stop() return 1, None except BaseException as msg: logging.writeToFile("List Container ....... %s" % str(msg)) return 0, str(msg) ##### N8N Container def check_container_health(self, container_name, max_retries=3, delay=80): """ Check if a container is running, accepting healthy, unhealthy, and starting states Total wait time will be 4 minutes (3 retries * 80 seconds) """ try: # Format container name to match Docker's naming convention formatted_name = f"{self.data['ServiceName']}-{container_name}-1" logging.writeToFile(f'Checking container health for: {formatted_name}') for attempt in range(max_retries): client = docker.from_env() container = client.containers.get(formatted_name) if container.status == 'running': health = container.attrs.get('State', {}).get('Health', {}).get('Status') # Accept healthy, unhealthy, and starting states as long as container is running if health in ['healthy', 'unhealthy', 'starting'] or health is None: logging.writeToFile(f'Container {formatted_name} is running with status: {health}') return True else: health_logs = container.attrs.get('State', {}).get('Health', {}).get('Log', []) if health_logs: last_log = health_logs[-1] logging.writeToFile(f'Container health check failed: {last_log.get("Output", "")}') logging.writeToFile(f'Container {formatted_name} status: {container.status}, health: {health}, attempt {attempt + 1}/{max_retries}') time.sleep(delay) return False except docker.errors.NotFound: logging.writeToFile(f'Container {formatted_name} not found') return False except Exception as e: logging.writeToFile(f'Error checking container health: {str(e)}') return False def verify_system_resources(self): try: # Check available disk space using root access command = "df -B 1G /home/docker --output=avail | tail -1" result, output = ProcessUtilities.outputExecutioner(command, None, None, None, 1) if result == 0: raise DockerDeploymentError("Failed to check disk space") available_gb = int(output.strip()) if available_gb < 5: # Require minimum 5GB free space raise DockerDeploymentError( f"Insufficient disk space. Need at least 5GB but only {available_gb}GB available.", self.ERROR_VOLUME_FAILED ) # Check if Docker is running and accessible command = "systemctl is-active docker" result, docker_status = ProcessUtilities.outputExecutioner(command, None, None, None, 1) if result == 0: raise DockerDeploymentError("Failed to check Docker status") if docker_status.strip() != "active": raise DockerDeploymentError("Docker service is not running") # Check Docker system info for resource limits command = "docker info --format '{{.MemTotal}}'" result, total_memory = ProcessUtilities.outputExecutioner(command, None, None, None, 1) if result == 0: raise DockerDeploymentError("Failed to get Docker memory info") # Convert total_memory from bytes to MB total_memory_mb = int(total_memory.strip()) / (1024 * 1024) # Calculate required memory from site and MySQL requirements required_memory = int(self.data['MemoryMySQL']) + int(self.data['MemorySite']) if total_memory_mb < required_memory: raise DockerDeploymentError( f"Insufficient memory. Need {required_memory}MB but only {int(total_memory_mb)}MB available", 'INSUFFICIENT_MEMORY' ) # Verify Docker group and permissions command = "getent group docker" result, docker_group = ProcessUtilities.outputExecutioner(command, None, None, None, 1) if result == 0 or not docker_group: raise DockerDeploymentError("Docker group does not exist") return True except DockerDeploymentError as e: raise e except Exception as e: raise DockerDeploymentError(f"Resource verification failed: {str(e)}") def setup_docker_environment(self): try: # Create docker directory with root command = f"mkdir -p /home/docker/{self.data['finalURL']}" ProcessUtilities.outputExecutioner(command, None, None, None, 1) # Set proper permissions command = f"chown -R {self.data['externalApp']}:docker /home/docker/{self.data['finalURL']}" ProcessUtilities.outputExecutioner(command, None, None, None, 1) # Create docker network if doesn't exist command = "docker network ls | grep cyberpanel" network_exists = ProcessUtilities.outputExecutioner(command, None, None, None, 1) if not network_exists: command = "docker network create cyberpanel" ProcessUtilities.outputExecutioner(command, None, None, None, 1) return True except Exception as e: raise DockerDeploymentError(f"Environment setup failed: {str(e)}") def deploy_containers(self): try: # Write docker-compose file command = f"cat > {self.data['ComposePath']} << 'EOF'\n{self.data['ComposeContent']}\nEOF" ProcessUtilities.outputExecutioner(command, None, None, None, 1) # Set proper permissions on compose file command = f"chmod 600 {self.data['ComposePath']} && chown root:root {self.data['ComposePath']}" ProcessUtilities.outputExecutioner(command, None, None, None, 1) # Deploy with docker-compose command = f"cd {os.path.dirname(self.data['ComposePath'])} && docker-compose up -d" result = ProcessUtilities.outputExecutioner(command, None, None, None, 1) if "error" in result.lower(): raise DockerDeploymentError(f"Container deployment failed: {result}") return True except Exception as e: raise DockerDeploymentError(f"Deployment failed: {str(e)}") def cleanup_failed_deployment(self): try: # Stop and remove containers command = f"cd {os.path.dirname(self.data['ComposePath'])} && docker-compose down -v" ProcessUtilities.outputExecutioner(command, None, None, None, 1) # Remove docker directory command = f"rm -rf /home/docker/{self.data['finalURL']}" ProcessUtilities.outputExecutioner(command, None, None, None, 1) # Remove compose file command = f"rm -f {self.data['ComposePath']}" ProcessUtilities.outputExecutioner(command, None, None, None, 1) return True except Exception as e: logging.writeToFile(f"Cleanup failed: {str(e)}") return False def monitor_deployment(self): try: # Format container names n8n_container_name = f"{self.data['ServiceName']}-{self.data['ServiceName']}-1" db_container_name = f"{self.data['ServiceName']}-{self.data['ServiceName']}-db-1" logging.writeToFile(f'Monitoring containers: {n8n_container_name} and {db_container_name}') # Check container health command = f"docker ps -a --filter name={self.data['ServiceName']} --format '{{{{.Status}}}}'" result, status = ProcessUtilities.outputExecutioner(command, None, None, None, 1) # Only raise error if container is exited if "exited" in status: # Get container logs command = f"docker logs {n8n_container_name}" result, logs = ProcessUtilities.outputExecutioner(command, None, None, None, 1) raise DockerDeploymentError(f"Container exited. Logs: {logs}") # Wait for database to be ready max_retries = 30 retry_count = 0 db_ready = False while retry_count < max_retries: # Check if database container is ready command = f"docker exec {db_container_name} pg_isready -U postgres" result, output = ProcessUtilities.outputExecutioner(command, None, None, None, 1) if "accepting connections" in output: db_ready = True break # Check container status command = f"docker inspect --format='{{{{.State.Status}}}}' {db_container_name}" result, db_status = ProcessUtilities.outputExecutioner(command, None, None, None, 1) # Only raise error if database container is in a failed state if db_status == 'exited': raise DockerDeploymentError(f"Database container is in {db_status} state") retry_count += 1 time.sleep(2) logging.writeToFile(f'Waiting for database to be ready, attempt {retry_count}/{max_retries}') if not db_ready: raise DockerDeploymentError("Database failed to become ready within timeout period") # Check n8n container status command = f"docker inspect --format='{{{{.State.Status}}}}' {n8n_container_name}" result, n8n_status = ProcessUtilities.outputExecutioner(command, None, None, None, 1) # Only raise error if n8n container is in a failed state if n8n_status == 'exited': raise DockerDeploymentError(f"n8n container is in {n8n_status} state") logging.writeToFile(f'Deployment monitoring completed successfully. n8n status: {n8n_status}, database ready: {db_ready}') return True except Exception as e: logging.writeToFile(f'Error during monitoring: {str(e)}') raise DockerDeploymentError(f"Monitoring failed: {str(e)}") def handle_deployment_failure(self, error, cleanup=True): """ Handle deployment failures and attempt recovery """ try: logging.writeToFile(f'Deployment failed: {str(error)}') if cleanup: self.cleanup_failed_deployment() if isinstance(error, DockerDeploymentError): if error.error_code == self.ERROR_DOCKER_NOT_INSTALLED: # Attempt to install Docker execPath = "/usr/local/CyberCP/bin/python /usr/local/CyberCP/dockerManager/dockerInstall.py" ProcessUtilities.executioner(execPath) return True elif error.error_code == self.ERROR_PORT_IN_USE: # Find next available port new_port = int(self.data['port']) + 1 while new_port < 65535: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) result = sock.connect_ex(('127.0.0.1', new_port)) sock.close() if result != 0: self.data['port'] = str(new_port) return True new_port += 1 elif error.error_code == self.ERROR_DB_FAILED: # Attempt database recovery return self.recover_database() return False except Exception as e: logging.writeToFile(f'Error during failure handling: {str(e)}') return False def recover_database(self): """ Attempt to recover the database container """ try: client = docker.from_env() db_container_name = f"{self.data['ServiceName']}-db" try: db_container = client.containers.get(db_container_name) if db_container.status == 'running': exec_result = db_container.exec_run( 'pg_isready -U postgres' ) if exec_result.exit_code != 0: db_container.restart() time.sleep(10) if self.check_container_health(db_container_name): return True except docker.errors.NotFound: pass return False except Exception as e: logging.writeToFile(f'Database recovery failed: {str(e)}') return False def log_deployment_metrics(self, metrics): """ Log deployment metrics for analysis """ if metrics: try: log_file = f"/var/log/cyberpanel/docker/{self.data['ServiceName']}_metrics.json" os.makedirs(os.path.dirname(log_file), exist_ok=True) with open(log_file, 'w') as f: json.dump(metrics, f, indent=2) except Exception as e: logging.writeToFile(f'Error logging metrics: {str(e)}') def DeployN8NContainer(self): """ Main deployment method with error handling """ max_retries = 3 current_try = 0 while current_try < max_retries: try: logging.statusWriter(self.JobID, 'Starting deployment verification...,0') # Check Docker installation command = 'docker --help' result = ProcessUtilities.outputExecutioner(command) if result.find("not found") > -1: if os.path.exists(ProcessUtilities.debugPath): logging.writeToFile(f'About to run docker install function...') # Call InstallDocker to install Docker install_result, error = self.InstallDocker() if not install_result: logging.statusWriter(self.JobID, f'Failed to install Docker: {error} [404]') return 0 logging.statusWriter(self.JobID, 'Docker installation verified...,20') # Verify system resources self.verify_system_resources() logging.statusWriter(self.JobID, 'System resources verified...,10') # Create directories command = f"mkdir -p /home/docker/{self.data['finalURL']}" result, message = ProcessUtilities.outputExecutioner(command, None, None, None, 1) if result == 0: raise DockerDeploymentError(f"Failed to create directories: {message}") logging.statusWriter(self.JobID, 'Directories created...,30') # Generate and write docker-compose file self.data['ServiceName'] = self.data["SiteName"].replace(' ', '-') compose_config = self.generate_compose_config() TempCompose = f'/home/cyberpanel/{self.data["finalURL"]}-docker-compose.yml' with open(TempCompose, 'w') as f: f.write(compose_config) command = f"mv {TempCompose} {self.data['ComposePath']}" result, message = ProcessUtilities.outputExecutioner(command, None, None, None, 1) if result == 0: raise DockerDeploymentError(f"Failed to move compose file: {message}") command = f"chmod 600 {self.data['ComposePath']} && chown root:root {self.data['ComposePath']}" ProcessUtilities.executioner(command, 'root', True) logging.statusWriter(self.JobID, 'Docker compose file created...,40') # Deploy containers if ProcessUtilities.decideDistro() == ProcessUtilities.cent8 or ProcessUtilities.decideDistro() == ProcessUtilities.centos: dockerCommand = 'docker compose' else: dockerCommand = 'docker-compose' command = f"{dockerCommand} -f {self.data['ComposePath']} -p '{self.data['SiteName']}' up -d" result, message = ProcessUtilities.outputExecutioner(command, None, None, None, 1) if result == 0: raise DockerDeploymentError(f"Failed to deploy containers: {message}") logging.statusWriter(self.JobID, 'Containers deployed...,60') # Wait for containers to be healthy time.sleep(25) if not self.check_container_health(f"{self.data['ServiceName']}-db") or \ not self.check_container_health(self.data['ServiceName']): raise DockerDeploymentError("Containers failed to reach healthy state", self.ERROR_CONTAINER_FAILED) logging.statusWriter(self.JobID, 'Containers healthy...,70') # Setup proxy execPath = "/usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/DockerSites.py" execPath = execPath + f" SetupProxy --port {self.data['port']}" ProcessUtilities.executioner(execPath) logging.statusWriter(self.JobID, 'Proxy configured...,80') # Setup ht access execPath = "/usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/DockerSites.py" execPath = execPath + f" SetupHTAccess --port {self.data['port']} --htaccess {self.data['htaccessPath']}" ProcessUtilities.executioner(execPath, self.data['externalApp']) logging.statusWriter(self.JobID, 'HTAccess configured...,90') # Restart web server from plogical.installUtilities import installUtilities installUtilities.reStartLiteSpeedSocket() # Monitor deployment metrics = self.monitor_deployment() self.log_deployment_metrics(metrics) logging.statusWriter(self.JobID, 'Deployment completed successfully. [200]') return True except DockerDeploymentError as e: logging.writeToFile(f'Deployment error: {str(e)}') if self.handle_deployment_failure(e): current_try += 1 continue else: logging.statusWriter(self.JobID, f'Deployment failed: {str(e)} [404]') return False except Exception as e: logging.writeToFile(f'Unexpected error: {str(e)}') self.handle_deployment_failure(e) logging.statusWriter(self.JobID, f'Deployment failed: {str(e)} [404]') return False logging.statusWriter(self.JobID, f'Deployment failed after {max_retries} attempts [404]') return False def generate_compose_config(self): """ Generate the docker-compose configuration with improved security and reliability """ postgres_config = { 'image': 'postgres:16-alpine', 'user': 'root', 'healthcheck': { 'test': ["CMD-SHELL", "pg_isready -U postgres"], 'interval': '10s', 'timeout': '5s', 'retries': 5, 'start_period': '30s' }, 'environment': { 'POSTGRES_USER': 'postgres', 'POSTGRES_PASSWORD': self.data['MySQLPassword'], 'POSTGRES_DB': self.data['MySQLDBName'] } } n8n_config = { 'image': 'docker.n8n.io/n8nio/n8n', 'user': 'root', 'healthcheck': { 'test': ["CMD", "wget", "--spider", "http://localhost:5678"], 'interval': '20s', 'timeout': '10s', 'retries': 3 }, 'environment': { 'DB_TYPE': 'postgresdb', 'DB_POSTGRESDB_HOST': f"{self.data['ServiceName']}-db", 'DB_POSTGRESDB_PORT': '5432', 'DB_POSTGRESDB_DATABASE': self.data['MySQLDBName'], 'DB_POSTGRESDB_USER': 'postgres', 'DB_POSTGRESDB_PASSWORD': self.data['MySQLPassword'], 'N8N_HOST': self.data['finalURL'], 'NODE_ENV': 'production', 'WEBHOOK_URL': f"https://{self.data['finalURL']}", 'N8N_PUSH_BACKEND': 'sse', 'GENERIC_TIMEZONE': 'UTC', 'N8N_ENCRYPTION_KEY': 'auto', 'N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS': 'true', 'DB_POSTGRESDB_SCHEMA': 'public' } } return f'''version: '3.8' volumes: db_storage: driver: local n8n_storage: driver: local services: '{self.data['ServiceName']}-db': image: {postgres_config['image']} user: {postgres_config['user']} restart: always healthcheck: test: {postgres_config['healthcheck']['test']} interval: {postgres_config['healthcheck']['interval']} timeout: {postgres_config['healthcheck']['timeout']} retries: {postgres_config['healthcheck']['retries']} start_period: {postgres_config['healthcheck']['start_period']} environment: - POSTGRES_USER={postgres_config['environment']['POSTGRES_USER']} - POSTGRES_PASSWORD={postgres_config['environment']['POSTGRES_PASSWORD']} - POSTGRES_DB={postgres_config['environment']['POSTGRES_DB']} volumes: - "/home/docker/{self.data['finalURL']}/db:/var/lib/postgresql/data" networks: - n8n-network deploy: resources: limits: cpus: '{self.data["CPUsMySQL"]}' memory: {self.data["MemoryMySQL"]}M '{self.data['ServiceName']}': image: {n8n_config['image']} user: {n8n_config['user']} restart: always healthcheck: test: {n8n_config['healthcheck']['test']} interval: {n8n_config['healthcheck']['interval']} timeout: {n8n_config['healthcheck']['timeout']} retries: {n8n_config['healthcheck']['retries']} environment: - DB_TYPE={n8n_config['environment']['DB_TYPE']} - DB_POSTGRESDB_HOST={n8n_config['environment']['DB_POSTGRESDB_HOST']} - DB_POSTGRESDB_PORT={n8n_config['environment']['DB_POSTGRESDB_PORT']} - DB_POSTGRESDB_DATABASE={n8n_config['environment']['DB_POSTGRESDB_DATABASE']} - DB_POSTGRESDB_USER={n8n_config['environment']['DB_POSTGRESDB_USER']} - DB_POSTGRESDB_PASSWORD={n8n_config['environment']['DB_POSTGRESDB_PASSWORD']} - DB_POSTGRESDB_SCHEMA={n8n_config['environment']['DB_POSTGRESDB_SCHEMA']} - N8N_HOST={n8n_config['environment']['N8N_HOST']} - NODE_ENV={n8n_config['environment']['NODE_ENV']} - WEBHOOK_URL={n8n_config['environment']['WEBHOOK_URL']} - N8N_PUSH_BACKEND={n8n_config['environment']['N8N_PUSH_BACKEND']} - GENERIC_TIMEZONE={n8n_config['environment']['GENERIC_TIMEZONE']} - N8N_ENCRYPTION_KEY={n8n_config['environment']['N8N_ENCRYPTION_KEY']} - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS={n8n_config['environment']['N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS']} ports: - "{self.data['port']}:5678" depends_on: - {self.data['ServiceName']}-db volumes: - "/home/docker/{self.data['finalURL']}/data:/home/node/.n8n" networks: - n8n-network deploy: resources: limits: cpus: '{self.data["CPUsSite"]}' memory: {self.data["MemorySite"]}M networks: n8n-network: driver: bridge name: {self.data['ServiceName']}_network''' def Main(): try: parser = argparse.ArgumentParser(description='CyberPanel Docker Sites') parser.add_argument('function', help='Specify a function to call!') parser.add_argument('--port', help='') parser.add_argument('--htaccess', help='') parser.add_argument('--externalApp', help='') parser.add_argument('--domain', help='') args = parser.parse_args() if args.function == "SetupProxy": Docker_Sites.SetupProxy(args.port) elif args.function == 'SetupHTAccess': Docker_Sites.SetupHTAccess(args.port, args.htaccess) elif args.function == 'DeployWPDocker': # Takes # ComposePath, MySQLPath, MySQLRootPass, MySQLDBName, MySQLDBNUser, MySQLPassword, CPUsMySQL, MemoryMySQL, # port, SitePath, CPUsSite, MemorySite, SiteName # finalURL, blogTitle, adminUser, adminPassword, adminEmail, htaccessPath, externalApp data = { "JobID": '/home/cyberpanel/hey.txt', "ComposePath": "/home/docker.cyberpanel.net/docker-compose.yml", "MySQLPath": '/home/docker.cyberpanel.net/public_html/sqldocker', "MySQLRootPass": 'testdbwp12345', "MySQLDBName": 'testdbwp', "MySQLDBNUser": 'testdbwp', "MySQLPassword": 'testdbwp12345', "CPUsMySQL": '2', "MemoryMySQL": '512', "port": '8000', "SitePath": '/home/docker.cyberpanel.net/public_html/wpdocker', "CPUsSite": '2', "MemorySite": '512', "SiteName": 'wp docker test', "finalURL": 'docker.cyberpanel.net', "blogTitle": 'docker site', "adminUser": 'testdbwp', "adminPassword": 'testdbwp', "adminEmail": 'usman@cyberpersons.com', "htaccessPath": '/home/docker.cyberpanel.net/public_html/.htaccess', "externalApp": 'docke8463', "docRoot": "/home/docker.cyberpanel.net" } ds = Docker_Sites('', data) ds.DeployN8NContainer() elif args.function == 'DeleteDockerApp': data = { "domain": args.domain} ds = Docker_Sites('', data) ds.DeleteDockerApp() except BaseException as msg: print(str(msg)) pass if __name__ == "__main__": Main()
| ver. 1.4 |
Github
|
.
| PHP 8.2.28 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings