import getpass import glob import os import random import re import string import subprocess from subprocess import CalledProcessError from wo.cli.plugins.sitedb import * from wo.cli.plugins.stack import WOStackController from wo.core.aptget import WOAptGet from wo.core.fileutils import WOFileUtils from wo.core.git import WOGit from wo.core.logging import Log from wo.core.mysql import * from wo.core.services import WOService from wo.core.shellexec import CommandExecutionError, WOShellExec from wo.core.sslutils import SSL from wo.core.variables import WOVariables class SiteError(Exception): """Custom Exception Occured when setting up site""" def __init__(self, message): self.message = message def __str__(self): return repr(self.message) def pre_run_checks(self): # Check nginx configuration Log.info(self, "Running pre-update checks, please wait...") try: Log.debug(self, "checking NGINX configuration ...") FNULL = open('/dev/null', 'w') ret = subprocess.check_call(["nginx", "-t"], stdout=FNULL, stderr=subprocess.STDOUT) except CalledProcessError as e: Log.debug(self, "{0}".format(str(e))) raise SiteError("nginx configuration check failed.") def check_domain_exists(self, domain): if getSiteInfo(self, domain): return True return False def setupdomain(self, data): # for debug purpose # for key, value in data.items() : # print (key, value) wo_domain_name = data['site_name'] wo_site_webroot = data['webroot'] # Check if nginx configuration already exists # if os.path.isfile('/etc/nginx/sites-available/{0}' # .format(wo_domain_name)): # raise SiteError("nginx configuration already exists for site") Log.info(self, "Setting up NGINX configuration \t", end='') # write nginx config for file try: wo_site_nginx_conf = open('/etc/nginx/sites-available/{0}' .format(wo_domain_name), encoding='utf-8', mode='w') if not data['php73']: self.app.render((data), 'virtualconf.mustache', out=wo_site_nginx_conf) else: self.app.render((data), 'virtualconf-php7.mustache', out=wo_site_nginx_conf) wo_site_nginx_conf.close() except IOError as e: Log.debug(self, "{0}".format(e)) raise SiteError("create nginx configuration failed for site") except Exception as e: Log.debug(self, "{0}".format(e)) raise SiteError("create nginx configuration failed for site") finally: # Check nginx -t and return status over it try: Log.debug(self, "Checking generated nginx conf, please wait...") FNULL = open('/dev/null', 'w') ret = subprocess.check_call(["nginx", "-t"], stdout=FNULL, stderr=subprocess.STDOUT) Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") except CalledProcessError as e: Log.debug(self, "{0}".format(str(e))) Log.info(self, "[" + Log.ENDC + Log.FAIL + "Fail" + Log.OKBLUE + "]") raise SiteError("created nginx configuration failed for site." " check with `nginx -t`") # create symbolic link for WOFileUtils.create_symlink(self, ['/etc/nginx/sites-available/{0}' .format(wo_domain_name), '/etc/nginx/sites-enabled/{0}' .format(wo_domain_name)]) # Creating htdocs & logs directory Log.info(self, "Setting up webroot \t\t", end='') try: if not os.path.exists('{0}/htdocs'.format(wo_site_webroot)): os.makedirs('{0}/htdocs'.format(wo_site_webroot)) if not os.path.exists('{0}/logs'.format(wo_site_webroot)): os.makedirs('{0}/logs'.format(wo_site_webroot)) if not os.path.exists('{0}/conf/nginx'.format(wo_site_webroot)): os.makedirs('{0}/conf/nginx'.format(wo_site_webroot)) WOFileUtils.create_symlink(self, ['/var/log/nginx/{0}.access.log' .format(wo_domain_name), '{0}/logs/access.log' .format(wo_site_webroot)]) WOFileUtils.create_symlink(self, ['/var/log/nginx/{0}.error.log' .format(wo_domain_name), '{0}/logs/error.log' .format(wo_site_webroot)]) except Exception as e: Log.debug(self, "{0}".format(e)) raise SiteError("setup webroot failed for site") finally: # TODO Check if directories are setup if (os.path.exists('{0}/htdocs'.format(wo_site_webroot)) and os.path.exists('{0}/logs'.format(wo_site_webroot))): Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") else: Log.info(self, "[" + Log.ENDC + "Fail" + Log.OKBLUE + "]") raise SiteError("setup webroot failed for site") def setupdatabase(self, data): wo_domain_name = data['site_name'] wo_random = (''.join(random.sample(string.ascii_uppercase + string.ascii_lowercase + string.digits, 24))) wo_replace_dot = wo_domain_name.replace('.', '_') prompt_dbname = self.app.config.get('mysql', 'db-name') prompt_dbuser = self.app.config.get('mysql', 'db-user') wo_mysql_grant_host = self.app.config.get('mysql', 'grant-host') wo_db_name = '' wo_db_username = '' wo_db_password = '' if prompt_dbname == 'True' or prompt_dbname == 'true': try: wo_db_name = input('Enter the MySQL database name [{0}]: ' .format(wo_replace_dot)) except EOFError as e: Log.debug(self, "{0}".format(e)) raise SiteError("Unable to input database name") if not wo_db_name: wo_db_name = wo_replace_dot if prompt_dbuser == 'True' or prompt_dbuser == 'true': try: wo_db_username = input('Enter the MySQL database user name [{0}]: ' .format(wo_replace_dot)) wo_db_password = getpass.getpass(prompt='Enter the MySQL database' ' password [{0}]: ' .format(wo_random)) except EOFError as e: Log.debug(self, "{0}".format(e)) raise SiteError("Unable to input database credentials") if not wo_db_username: wo_db_username = wo_replace_dot if not wo_db_password: wo_db_password = wo_random if len(wo_db_username) > 16: Log.debug(self, 'Autofix MySQL username (ERROR 1470 (HY000)),' ' please wait') wo_db_username = (wo_db_name[0:6] + generate_random()) # create MySQL database Log.info(self, "Setting up database\t\t", end='') Log.debug(self, "Creating database {0}".format(wo_db_name)) try: if WOMysql.check_db_exists(self, wo_db_name): Log.debug(self, "Database already exists, Updating DB_NAME .. ") wo_db_name = (wo_db_name[0:6] + generate_random()) wo_db_username = (wo_db_name[0:6] + generate_random()) except MySQLConnectionError as e: raise SiteError("MySQL Connectivity problem occured") try: WOMysql.execute(self, "create database `{0}`" .format(wo_db_name)) except StatementExcecutionError as e: Log.info(self, "[" + Log.ENDC + Log.FAIL + "Failed" + Log.OKBLUE + "]") raise SiteError("create database execution failed") # Create MySQL User Log.debug(self, "Creating user {0}".format(wo_db_username)) Log.debug(self, "create user `{0}`@`{1}` identified by ''" .format(wo_db_username, wo_mysql_grant_host)) try: WOMysql.execute(self, "create user `{0}`@`{1}` identified by '{2}'" .format(wo_db_username, wo_mysql_grant_host, wo_db_password), log=False) except StatementExcecutionError as e: Log.info(self, "[" + Log.ENDC + Log.FAIL + "Failed" + Log.OKBLUE + "]") raise SiteError("creating user failed for database") # Grant permission Log.debug(self, "Setting up user privileges") try: WOMysql.execute(self, "grant all privileges on `{0}`.* to `{1}`@`{2}`" .format(wo_db_name, wo_db_username, wo_mysql_grant_host)) except StatementExcecutionError as e: Log.info(self, "[" + Log.ENDC + Log.FAIL + "Failed" + Log.OKBLUE + "]") SiteError("grant privileges to user failed for database ") Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") data['wo_db_name'] = wo_db_name data['wo_db_user'] = wo_db_username data['wo_db_pass'] = wo_db_password data['wo_db_host'] = WOVariables.wo_mysql_host data['wo_mysql_grant_host'] = wo_mysql_grant_host return(data) def setupwordpress(self, data): wo_domain_name = data['site_name'] wo_site_webroot = data['webroot'] prompt_wpprefix = self.app.config.get('wordpress', 'prefix') wo_wp_user = self.app.config.get('wordpress', 'user') wo_wp_pass = self.app.config.get('wordpress', 'password') wo_wp_email = self.app.config.get('wordpress', 'email') # Random characters wo_random = (''.join(random.sample(string.ascii_uppercase + string.ascii_lowercase + string.digits, 15))) wo_wp_prefix = '' # wo_wp_user = '' # wo_wp_pass = '' if 'wp-user' in data.keys() and data['wp-user']: wo_wp_user = data['wp-user'] if 'wp-email' in data.keys() and data['wp-email']: wo_wp_email = data['wp-email'] if 'wp-pass' in data.keys() and data['wp-pass']: wo_wp_pass = data['wp-pass'] Log.info(self, "Downloading WordPress \t\t", end='') WOFileUtils.chdir(self, '{0}/htdocs/'.format(wo_site_webroot)) try: if WOShellExec.cmd_exec(self, "wp --allow-root core" " download"): pass else: Log.info(self, "[" + Log.ENDC + Log.FAIL + "Fail" + Log.OKBLUE + "]") raise SiteError("download WordPress core failed") except CommandExecutionError as e: Log.info(self, "[" + Log.ENDC + Log.FAIL + "Fail" + Log.OKBLUE + "]") raise SiteError(self, "download WordPress core failed") Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") if not (data['wo_db_name'] and data['wo_db_user'] and data['wo_db_pass']): data = setupdatabase(self, data) if prompt_wpprefix == 'True' or prompt_wpprefix == 'true': try: wo_wp_prefix = input('Enter the WordPress table prefix [wp_]: ') while not re.match('^[A-Za-z0-9_]*$', wo_wp_prefix): Log.warn(self, "table prefix can only " "contain numbers, letters, and underscores") wo_wp_prefix = input('Enter the WordPress table prefix [wp_]: ' ) except EOFError as e: Log.debug(self, "{0}".format(e)) raise SiteError("input table prefix failed") if not wo_wp_prefix: wo_wp_prefix = 'wp_' # Modify wp-config.php & move outside the webroot WOFileUtils.chdir(self, '{0}/htdocs/'.format(wo_site_webroot)) Log.debug(self, "Setting up wp-config file") if not data['multisite']: Log.debug(self, "Generating wp-config for WordPress Single site") Log.debug(self, "bash -c \"php {0} --allow-root " .format(WOVariables.wo_wpcli_path) + "config create " + "--dbname=\'{0}\' --dbprefix=\'{1}\' --dbuser=\'{2}\' " "--dbhost=\'{3}\' " .format(data['wo_db_name'], wo_wp_prefix, data['wo_db_user'], data['wo_db_host']) + "--dbpass=\'{0}\' " "--extra-php< {1}/{0}.gz" .format(data['wo_db_name'], backup_path)): Log.info(self, "[" + Log.ENDC + Log.FAIL + "Fail" + Log.OKBLUE + "]") raise SiteError("mysqldump failed to backup database") except CommandExecutionError as e: Log.debug(self, str(e)) Log.info(self, "[" + Log.ENDC + "Fail" + Log.OKBLUE + "]") raise SiteError("mysqldump failed to backup database") Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") # move wp-config.php/wo-config.php to backup if data['currsitetype'] in ['mysql', 'proxy']: if data['php73'] is True and not data['wp']: WOFileUtils.copyfile(self, configfiles[0], backup_path) else: WOFileUtils.mvfile(self, configfiles[0], backup_path) else: WOFileUtils.copyfile(self, configfiles[0], backup_path) def site_package_check(self, stype): apt_packages = [] packages = [] stack = WOStackController() stack.app = self.app if stype in ['html', 'proxy', 'php', 'mysql', 'wp', 'wpsubdir', 'wpsubdomain', 'php73']: Log.debug(self, "Setting apt_packages variable for Nginx") # Check if server has nginx-custom package if not (WOAptGet.is_installed(self, 'nginx-custom') or WOAptGet.is_installed(self, 'nginx-mainline')): # check if Server has nginx-plus installed if WOAptGet.is_installed(self, 'nginx-plus'): # do something # do post nginx installation configuration Log.info(self, "NGINX PLUS Detected ...") apt = ["nginx-plus"] + WOVariables.wo_nginx # apt_packages = apt_packages + WOVariables.wo_nginx stack.post_pref(apt, packages) elif WOAptGet.is_installed(self, 'nginx'): Log.info(self, "WordOps detected a previously" "installed Nginx package. " "It may or may not have required modules. " "\nIf you need help, please create an issue at " "https://github.com/WordOps/WordOps/issues/ \n") apt = ["nginx"] + WOVariables.wo_nginx # apt_packages = apt_packages + WOVariables.wo_nginx stack.post_pref(apt, packages) else: apt_packages = apt_packages + WOVariables.wo_nginx else: # Fix for Nginx white screen death if not WOFileUtils.grep(self, '/etc/nginx/fastcgi_params', 'SCRIPT_FILENAME'): with open('/etc/nginx/fastcgi_params', encoding='utf-8', mode='a') as wo_nginx: wo_nginx.write('fastcgi_param \tSCRIPT_FILENAME ' '\t$request_filename;\n') if self.app.pargs.php and self.app.pargs.php73: Log.error( self, "Error: two different PHP versions cannot be " "combined within the same WordOps site") if not self.app.pargs.php73 and stype in ['php', 'mysql', 'wp', 'wpsubdir', 'wpsubdomain']: Log.debug(self, "Setting apt_packages variable for PHP 7.2") if not WOAptGet.is_installed(self, 'php7.2-fpm'): if not WOAptGet.is_installed(self, 'php7.3-fpm'): apt_packages = apt_packages + WOVariables.wo_php + \ WOVariables.wo_php_extra else: apt_packages = apt_packages + WOVariables.wo_php if self.app.pargs.php73 and stype in ['mysql', 'wp', 'wpsubdir', 'wpsubdomain']: Log.debug(self, "Setting apt_packages variable for PHP 7.3") if not WOAptGet.is_installed(self, 'php7.3-fpm'): if not WOAptGet.is_installed(self, 'php7.2-fpm'): apt_packages = apt_packages + WOVariables.wo_php + \ WOVariables.wo_php73 + WOVariables.wo_php_extra else: apt_packages = apt_packages + WOVariables.wo_php73 if stype in ['mysql', 'wp', 'wpsubdir', 'wpsubdomain']: Log.debug(self, "Setting apt_packages variable for MySQL") if not WOShellExec.cmd_exec(self, "/usr/bin/mysqladmin ping"): apt_packages = apt_packages + WOVariables.wo_mysql packages = packages + [["https://raw.githubusercontent.com/" "major/MySQLTuner-perl/master/" "mysqltuner.pl", "/usr/bin/mysqltuner", "MySQLTuner"]] if stype in ['wp', 'wpsubdir', 'wpsubdomain']: Log.debug(self, "Setting packages variable for WP-CLI") if not WOShellExec.cmd_exec(self, "command -v wp"): packages = packages + [["https://github.com/wp-cli/wp-cli/" "releases/download/v{0}/" "wp-cli-{0}.phar" .format(WOVariables.wo_wp_cli), "/usr/local/bin/wp", "WP-CLI"]] if self.app.pargs.wpredis: Log.debug(self, "Setting apt_packages variable for redis") if not WOAptGet.is_installed(self, 'redis-server'): apt_packages = apt_packages + WOVariables.wo_redis if (os.path.isfile("/etc/nginx/nginx.conf") and not os.path.isfile("/etc/nginx/common/redis-php72.conf")): data = dict() Log.debug(self, 'Writting the nginx configuration to ' 'file /etc/nginx/common/redis-php72.conf') wo_nginx = open('/etc/nginx/common/redis-php72.conf', encoding='utf-8', mode='w') self.app.render((data), 'redis.mustache', out=wo_nginx) wo_nginx.close() if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): if not WOFileUtils.grep(self, "/etc/nginx/conf.d/" "upstream.conf", "redis"): with open("/etc/nginx/conf.d/upstream.conf", "a") as redis_file: redis_file.write("upstream redis {\n" " server 127.0.0.1:6379;\n" " keepalive 10;\n}") if (os.path.isfile("/etc/nginx/nginx.conf") and not os.path.isfile("/etc/nginx/conf.d/redis.conf")): with open("/etc/nginx/conf.d/redis.conf", "a") as redis_file: redis_file.write("# Log format Settings\n" "log_format rt_cache_redis '$remote_addr" " $upstream_response_time " "$srcache_fetch_status [$time_local] '\n" "'$http_host \"$request\" $status" " $body_bytes_sent '\n" "'\"$http_referer\" \"$http_user_agent\"';\n") if self.app.pargs.php73: Log.debug(self, "Setting apt_packages variable for PHP 7.3") if not WOAptGet.is_installed(self, 'php7.3-fpm'): if not WOAptGet.is_installed(self, 'php7.2-fpm'): apt_packages = apt_packages + WOVariables.wo_php + \ WOVariables.wo_php73 + WOVariables.wo_php_extra else: apt_packages = apt_packages + WOVariables.wo_php73 if (os.path.isdir("/etc/nginx/common") and not os.path.isfile("/etc/nginx/common/locations-wo.conf")): data = dict() Log.debug(self, 'Writting the nginx configuration to ' 'file /etc/nginx/common/locations-wo.conf') wo_nginx = open('/etc/nginx/common/locations-wo.conf', encoding='utf-8', mode='w') self.app.render((data), 'locations-php7.mustache', out=wo_nginx) wo_nginx.close() Log.debug(self, 'Writting the nginx configuration to ' 'file /etc/nginx/common/php73.conf') wo_nginx = open('/etc/nginx/common/php73.conf', encoding='utf-8', mode='w') self.app.render((data), 'php7.mustache', out=wo_nginx) wo_nginx.close() Log.debug(self, 'Writting the nginx configuration to ' 'file /etc/nginx/common/wpcommon-php73.conf') wo_nginx = open('/etc/nginx/common/wpcommon-php73.conf', encoding='utf-8', mode='w') self.app.render((data), 'wpcommon-php7.mustache', out=wo_nginx) wo_nginx.close() Log.debug(self, 'Writting the nginx configuration to ' 'file /etc/nginx/common/wpfc-php73.conf') wo_nginx = open('/etc/nginx/common/wpfc-php73.conf', encoding='utf-8', mode='w') self.app.render((data), 'wpfc-php7.mustache', out=wo_nginx) wo_nginx.close() Log.debug(self, 'Writting the nginx configuration to ' 'file /etc/nginx/common/wpsc-php73.conf') wo_nginx = open('/etc/nginx/common/wpsc-php73.conf', encoding='utf-8', mode='w') self.app.render((data), 'wpsc-php7.mustache', out=wo_nginx) wo_nginx.close() if (os.path.isfile("/etc/nginx/nginx.conf") and not os.path.isfile("/etc/nginx/common/redis-php73.conf")): data = dict() Log.debug(self, 'Writting the nginx configuration to ' 'file /etc/nginx/common/redis-php73.conf') wo_nginx = open('/etc/nginx/common/redis-php73.conf', encoding='utf-8', mode='w') self.app.render((data), 'redis-php7.mustache', out=wo_nginx) wo_nginx.close() if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): if not WOFileUtils.grep(self, "/etc/nginx/conf.d/upstream.conf", "php73"): with open("/etc/nginx/conf.d/upstream.conf", "a") as php_file: php_file.write("upstream php73 {\nserver" "unix:/var/run/php/php73-fpm.sock;\n}\n" "upstream debug73" " {\nserver 127.0.0.1:9173;\n}\n") return(stack.install(apt_packages=apt_packages, packages=packages, disp_msg=False)) def updatewpuserpassword(self, wo_domain, wo_site_webroot): wo_wp_user = '' wo_wp_pass = '' WOFileUtils.chdir(self, '{0}/htdocs/'.format(wo_site_webroot)) # Check if wo_domain is wordpress install try: is_wp = WOShellExec.cmd_exec(self, "wp --allow-root core" " version") except CommandExecutionError as e: Log.debug(self, "{0}".format(e)) raise SiteError("is WordPress site? check command failed ") # Exit if wo_domain is not wordpress install if not is_wp: Log.error(self, "{0} does not seem to be a WordPress site" .format(wo_domain)) try: wo_wp_user = input("Provide WordPress user name [admin]: ") except Exception as e: Log.debug(self, "{0}".format(e)) Log.error(self, "\nCould not update password") if wo_wp_user == "?": Log.info(self, "Fetching WordPress user list") try: WOShellExec.cmd_exec(self, "wp --allow-root user list " "--fields=user_login | grep -v user_login") except CommandExecutionError as e: Log.debug(self, "{0}".format(e)) raise SiteError("fetch wp userlist command failed") if not wo_wp_user: wo_wp_user = 'admin' try: is_user_exist = WOShellExec.cmd_exec(self, "wp --allow-root user list " "--fields=user_login | grep {0}$ " .format(wo_wp_user)) except CommandExecutionError as e: Log.debug(self, "{0}".format(e)) raise SiteError("if wp user exists check command failed") if is_user_exist: try: wo_wp_pass = getpass.getpass(prompt="Provide password for " "{0} user: " .format(wo_wp_user)) while not wo_wp_pass: wo_wp_pass = getpass.getpass(prompt="Provide password for " "{0} user: " .format(wo_wp_user)) except Exception as e: Log.debug(self, "{0}".format(e)) raise SiteError("failed to read password input ") try: WOShellExec.cmd_exec(self, "wp --allow-root user update {0}" " --user_pass={1}" .format(wo_wp_user, wo_wp_pass)) except CommandExecutionError as e: Log.debug(self, str(e)) raise SiteError("wp user password update command failed") Log.info(self, "Password updated successfully") else: Log.error(self, "Invalid WordPress user {0} for {1}." .format(wo_wp_user, wo_domain)) def display_cache_settings(self, data): if data['wpsc']: if data['multisite']: Log.info(self, "Configure WPSC:" "\t\thttp://{0}/wp-admin/network/settings.php?" "page=wpsupercache" .format(data['site_name'])) else: Log.info(self, "Configure WPSC:" "\t\thttp://{0}/wp-admin/options-general.php?" "page=wpsupercache" .format(data['site_name'])) if data['wpredis']: if data['multisite']: Log.info(self, "Configure redis-cache:" "\thttp://{0}/wp-admin/network/settings.php?" "page=redis-cache".format(data['site_name'])) else: Log.info(self, "Configure redis-cache:" "\thttp://{0}/wp-admin/options-general.php?" "page=redis-cache".format(data['site_name'])) Log.info(self, "Object Cache:\t\tEnable") def logwatch(self, logfiles): import zlib import base64 import time from wo.core import logwatch def callback(filename, lines): for line in lines: if line.find(':::') == -1: print(line) else: data = line.split(':::') try: print(data[0], data[1], zlib.decompress(base64.decodestring(data[2]))) except Exception as e: Log.debug(self, str(e)) Log.info(time.time(), 'caught exception rendering a new log line in %s' % filename) l = logwatch.LogWatcher(logfiles, callback) l.loop() def detSitePar(opts): """ Takes dictionary of parsed arguments 1.returns sitetype and cachetype 2. raises RuntimeError when wrong combination is used like "--wp --wpsubdir" or "--html --wp" """ sitetype, cachetype = '', '' typelist = list() cachelist = list() for key, val in opts.items(): if val and key in ['html', 'php', 'mysql', 'wp', 'wpsubdir', 'wpsubdomain', 'php73']: typelist.append(key) elif val and key in ['wpfc', 'wpsc', 'wpredis']: cachelist.append(key) if len(typelist) > 1 or len(cachelist) > 1: if len(cachelist) > 1: raise RuntimeError( "Could not determine cache type." "Multiple cache parameter entered") elif False not in [x in ('php', 'mysql', 'html') for x in typelist]: sitetype = 'mysql' if not cachelist: cachetype = 'basic' else: cachetype = cachelist[0] elif False not in [x in ('php73', 'mysql', 'html') for x in typelist]: sitetype = 'mysql' if not cachelist: cachetype = 'basic' else: cachetype = cachelist[0] elif False not in [x in ('php', 'mysql') for x in typelist]: sitetype = 'mysql' if not cachelist: cachetype = 'basic' else: cachetype = cachelist[0] elif False not in [x in ('php73', 'mysql') for x in typelist]: sitetype = 'mysql' if not cachelist: cachetype = 'basic' else: cachetype = cachelist[0] elif False not in [x in ('html', 'mysql') for x in typelist]: sitetype = 'mysql' if not cachelist: cachetype = 'basic' else: cachetype = cachelist[0] elif False not in [x in ('php', 'html') for x in typelist]: sitetype = 'php' if not cachelist: cachetype = 'basic' else: cachetype = cachelist[0] elif False not in [x in ('php73', 'html') for x in typelist]: sitetype = 'php73' if not cachelist: cachetype = 'basic' else: cachetype = cachelist[0] elif False not in [x in ('wp', 'wpsubdir') for x in typelist]: sitetype = 'wpsubdir' if not cachelist: cachetype = 'basic' else: cachetype = cachelist[0] elif False not in [x in ('wp', 'wpsubdomain') for x in typelist]: sitetype = 'wpsubdomain' if not cachelist: cachetype = 'basic' else: cachetype = cachelist[0] elif False not in [x in ('wp', 'php73') for x in typelist]: sitetype = 'wp' if not cachelist: cachetype = 'basic' else: cachetype = cachelist[0] elif False not in [x in ('wpsubdir', 'php73') for x in typelist]: sitetype = 'wpsubdir' if not cachelist: cachetype = 'basic' else: cachetype = cachelist[0] elif False not in [x in ('wpsubdomain', 'php73') for x in typelist]: sitetype = 'wpsubdomain' if not cachelist: cachetype = 'basic' else: cachetype = cachelist[0] else: raise RuntimeError("could not determine site and cache type") else: if not typelist and not cachelist: sitetype = None cachetype = None elif (not typelist or "php73" in typelist) and cachelist: sitetype = 'wp' cachetype = cachelist[0] elif typelist and (not cachelist): sitetype = typelist[0] cachetype = 'basic' else: sitetype = typelist[0] cachetype = cachelist[0] return (sitetype, cachetype) def generate_random(): wo_random10 = (''.join(random.sample(string.ascii_uppercase + string.ascii_lowercase + string.digits, 24))) return wo_random10 def deleteDB(self, dbname, dbuser, dbhost, exit=True): try: # Check if Database exists try: if WOMysql.check_db_exists(self, dbname): # Drop database if exists Log.debug(self, "dropping database `{0}`".format(dbname)) WOMysql.execute(self, "drop database `{0}`".format(dbname), errormsg='Unable to drop database {0}' .format(dbname)) except StatementExcecutionError as e: Log.debug(self, str(e)) Log.debug(self, "drop database failed") Log.info(self, "Database {0} not dropped".format(dbname)) except MySQLConnectionError as e: Log.debug(self, str(e)) Log.debug(self, "Mysql Connection problem occured") if dbuser != 'root': Log.debug(self, "dropping user `{0}`".format(dbuser)) try: WOMysql.execute(self, "drop user `{0}`@`{1}`" .format(dbuser, dbhost)) except StatementExcecutionError as e: Log.debug(self, str(e)) Log.debug(self, "drop database user failed") Log.info(self, "Database {0} not dropped".format(dbuser)) try: WOMysql.execute(self, "flush privileges") except StatementExcecutionError as e: Log.debug(self, str(e)) Log.debug(self, "drop database failed") Log.info(self, "Database {0} not dropped".format(dbname)) except Exception as e: Log.debug(self, str(e)) Log.error(self, "Error occured while deleting database", exit) def deleteWebRoot(self, webroot): # do some preprocessing before proceeding webroot = webroot.strip() if (webroot == "/var/www/" or webroot == "/var/www" or webroot == "/var/www/.." or webroot == "/var/www/."): Log.debug(self, "Tried to remove {0}, but didn't remove it" .format(webroot)) return False if os.path.isdir(webroot): Log.debug(self, "Removing {0}".format(webroot)) WOFileUtils.rm(self, webroot) return True Log.debug(self, "{0} does not exist".format(webroot)) return False def removeNginxConf(self, domain): if os.path.isfile('/etc/nginx/sites-available/{0}' .format(domain)): Log.debug(self, "Removing Nginx configuration") WOFileUtils.rm(self, '/etc/nginx/sites-enabled/{0}' .format(domain)) WOFileUtils.rm(self, '/etc/nginx/sites-available/{0}' .format(domain)) WOService.reload_service(self, 'nginx') WOGit.add(self, ["/etc/nginx"], msg="Deleted {0} " .format(domain)) def removeAcmeConf(self, domain): if os.path.isdir('/etc/letsencrypt/renewal/{0}_ecc' .format(domain)): Log.info(self, "Removing Acme configuration") Log.debug(self, "Removing Acme configuration") try: WOShellExec.cmd_exec(self, "/etc/letsencrypt/acme.sh " "--config-home " "'/etc/letsencrypt/config' " "--remove " "-d {0} --ecc" .format(domain)) except CommandExecutionError as e: Log.debug(self, "{0}".format(e)) Log.error(self, "Cert removal failed") WOFileUtils.rm(self, '/etc/letsencrypt/renewal/{0}_ecc' .format(domain)) WOFileUtils.rm(self, '/etc/letsencrypt/live/{0}' .format(domain)) WOFileUtils.rm(self, '/var/www/{0}/conf/nginx/ssl.conf' .format(domain)) WOFileUtils.rm(self, '/var/www/{0}/conf/nginx/ssl.conf.disabled' .format(domain)) WOFileUtils.rm(self, '/etc/nginx/conf.d/force-ssl-{0}.conf' .format(domain)) WOFileUtils.rm(self, '/etc/nginx/conf.d/force-ssl-{0}.conf.disabled' .format(domain)) WOGit.add(self, ["/etc/letsencrypt"], msg="Deleted {0} " .format(domain)) def doCleanupAction(self, domain='', webroot='', dbname='', dbuser='', dbhost=''): """ Removes the nginx configuration and database for the domain provided. doCleanupAction(self, domain='sitename', webroot='', dbname='', dbuser='', dbhost='') """ if domain: if os.path.isfile('/etc/nginx/sites-available/{0}' .format(domain)): removeNginxConf(self, domain) if os.path.isdir('/etc/letsencrypt/renewal/{0}_ecc' .format(domain)): removeAcmeConf(self, domain) if webroot: deleteWebRoot(self, webroot) if dbname: if not dbuser: raise SiteError("dbuser not provided") if not dbhost: raise SiteError("dbhost not provided") deleteDB(self, dbname, dbuser, dbhost) # setup letsencrypt for domain + www.domain def setupLetsEncrypt(self, wo_domain_name, subdomain=False, wildcard=False, wo_dns=False, wo_acme_dns='dns_cf'): if os.path.isfile("/etc/letsencrypt/" "renewal/{0}_ecc/" "fullchain.cer".format(wo_domain_name)): Log.debug(self, "Let's Encrypt certificate " "found for the domain: {0}" .format(wo_domain_name)) ssl = archivedCertificateHandle(self, wo_domain_name) else: keylenght = "{0}".format(self.app.config.get('letsencrypt', 'keylength')) if wo_dns: acme_mode = "--dns {0}".format(wo_acme_dns) Log.debug( self, "Validation : DNS mode with {0}".format(wo_acme_dns)) else: acme_mode = "-w /var/www/html" Log.debug(self, "Validation : Webroot mode") if subdomain: Log.info(self, "Issuing subdomain SSL cert with acme.sh") ssl = WOShellExec.cmd_exec(self, "/etc/letsencrypt/acme.sh " "--config-home " "'/etc/letsencrypt/config' " "--issue " "-d {0} {1} " "-k {2} -f" .format(wo_domain_name, acme_mode, keylenght)) elif wildcard: Log.info(self, "Issuing Wildcard SSL cert with acme.sh") ssl = WOShellExec.cmd_exec(self, "/etc/letsencrypt/acme.sh " "--config-home " "'/etc/letsencrypt/config' " "--issue " "-d {0} -d *.{0} --dns {1} " "-k {2} -f" .format(wo_domain_name, wo_acme_dns, keylenght)) else: Log.info(self, "Issuing domain SSL cert with acme.sh") ssl = WOShellExec.cmd_exec(self, "/etc/letsencrypt/acme.sh " "--config-home " "'/etc/letsencrypt/config' " "--issue " "-d {0} -d www.{0} {1} " "-k {2} -f" .format(wo_domain_name, acme_mode, keylenght)) if ssl: Log.info(self, "Deploying SSL cert with acme.sh") Log.debug(self, "Cert deployment for domain: {0}" .format(wo_domain_name)) try: WOShellExec.cmd_exec(self, "mkdir -p {0}/{1} && " "/etc/letsencrypt/acme.sh " "--config-home " "'/etc/letsencrypt/config' " "--install-cert -d {1} --ecc " "--cert-file {0}/{1}/cert.pem " "--key-file {0}/{1}/key.pem " "--fullchain-file " "{0}/{1}/fullchain.pem " "--ca-file {0}/{1}/ca.pem " "--reloadcmd " "\"nginx -t && " "service nginx restart\" " .format(WOVariables.wo_ssl_live, wo_domain_name)) Log.info( self, "Adding /var/www/{0}/conf/nginx/ssl.conf" .format(wo_domain_name)) sslconf = open("/var/www/{0}/conf/nginx/ssl.conf" .format(wo_domain_name), encoding='utf-8', mode='w') sslconf.write("listen 443 ssl http2;\n" "listen [::]:443 ssl http2;\n" "ssl_certificate {0}/{1}/fullchain.pem;\n" "ssl_certificate_key {0}/{1}/key.pem;\n" "ssl_trusted_certificate {0}/{1}/ca.pem;\n" "ssl_stapling_verify on;\n" .format(WOVariables.wo_ssl_live, wo_domain_name)) sslconf.close() # updateSiteInfo(self, wo_domain_name, ssl=True) WOGit.add(self, ["/etc/letsencrypt"], msg="Adding letsencrypt folder") except IOError as e: Log.debug(self, str(e)) Log.debug(self, "Error occured while generating " "ssl.conf") else: Log.error(self, "Unable to install certificate", False) Log.error(self, "Please make sure that your site is pointed to \n" "same server on which " "you are running Let\'s Encrypt Client " "\n to allow it to verify the site automatically.") # letsencrypt cert renewal def renewLetsEncrypt(self, wo_domain_name): ssl = WOShellExec.cmd_exec( self, "/etc/letsencrypt/acme.sh " "--config-home " "'/etc/letsencrypt/config' " "--renew -d {0} --ecc --force" .format(wo_domain_name)) # mail_list = '' if not ssl: Log.error(self, "ERROR : Let's Encrypt certificate renewal FAILED!", False) if (SSL.getExpirationDays(self, wo_domain_name) > 0): Log.error(self, "Your current certificate will expire within " + str(SSL.getExpirationDays(self, wo_domain_name)) + " days.", False) else: Log.error(self, "Your current certificate already expired!", False) # WOSendMail("wordops@{0}".format(wo_domain_name), wo_wp_email, # "[FAIL] HTTPS cert renewal {0}".format(wo_domain_name), # "Hi,\n\nHTTPS certificate renewal for https://{0} # was unsuccessful.".format(wo_domain_name) + # "\nPlease check the WordOps log for reason # The current expiry date is : " + # str(SSL.getExpirationDate(self, wo_domain_name)) + # "\n\nFor support visit https://wordops.net/support . # \n\nBest regards,\nYour WordOps Worker", files=mail_list, # port=25, isTls=False) Log.error(self, "Check the WO log for more details " "`tail /var/log/wo/wordops.log`") WOGit.add(self, ["/etc/letsencrypt"], msg="Adding letsencrypt folder") # WOSendMail("wordops@{0}".format(wo_domain_name), wo_wp_email, # "[SUCCESS] Let's Encrypt certificate renewal {0}".format(wo_domain_name), # "Hi,\n\nYour Let's Encrypt certificate has been renewed for # https://{0} .".format(wo_domain_name) + # "\nYour new certificate will expire on : " + # str(SSL.getExpirationDate(self, wo_domain_name)) + # "\n\nBest regards,\nYour WordOps Worker", files=mail_list, # port=25, isTls=False) # redirect= False to disable https redirection def setupHsts(self, wo_domain_name): Log.info( self, "Adding /var/www/{0}/conf/nginx/hsts.conf" .format(wo_domain_name)) hstsconf = open("/var/www/{0}/conf/nginx/hsts.conf" .format(wo_domain_name), encoding='utf-8', mode='w') hstsconf.write("more_set_headers " "\"Strict-Transport-Security: " "max-age=31536000; " "'includeSubDomains; " "preload\";") hstsconf.close() return 0 def httpsRedirect(self, wo_domain_name, redirect=True, wildcard=False): if redirect: if os.path.isfile("/etc/nginx/conf.d/force-ssl-{0}.conf.disabled" .format(wo_domain_name)): WOFileUtils.mvfile(self, "/etc/nginx/conf.d/force-ssl-{0}.conf.disabled" .format(wo_domain_name), "/etc/nginx/conf.d/force-ssl-{0}.conf" .format(wo_domain_name)) else: if wildcard: try: Log.info( self, "Adding /etc/nginx/conf.d/force-ssl-{0}.conf" .format(wo_domain_name)) sslconf = open("/etc/nginx/conf.d/force-ssl-{0}.conf" .format(wo_domain_name), encoding='utf-8', mode='w') sslconf.write("server {\n" "\tlisten 80;\n" + "\tlisten [::]:80;\n" + "\tserver_name *.{0} {0};\n" .format(wo_domain_name) + "\treturn 301 https://$host" "$request_uri;\n}") sslconf.close() except IOError as e: Log.debug(self, str(e)) Log.debug(self, "Error occured while generating " "/etc/nginx/conf.d/force-ssl-{0}.conf" .format(wo_domain_name)) else: try: Log.info( self, "Adding /etc/nginx/conf.d/force-ssl-{0}.conf" .format(wo_domain_name)) sslconf = open("/etc/nginx/conf.d/force-ssl-{0}.conf" .format(wo_domain_name), encoding='utf-8', mode='w') sslconf.write("server {\n" "\tlisten 80;\n" + "\tlisten [::]:80;\n" + "\tserver_name www.{0} {0};\n" .format(wo_domain_name) + "\treturn 301 https://{0}" .format(wo_domain_name)+"$request_uri;\n}") sslconf.close() except IOError as e: Log.debug(self, str(e)) Log.debug(self, "Error occured while generating " "/etc/nginx/conf.d/force-ssl-{0}.conf" .format(wo_domain_name)) Log.info(self, "Added HTTPS Force Redirection for Site " " http://{0}".format(wo_domain_name)) # Nginx Configation into GIT WOGit.add(self, ["/etc/nginx"], msg="Adding /etc/nginx/conf.d/" "force-ssl-{0}.conf".format(wo_domain_name)) else: if os.path.isfile("/etc/nginx/conf.d/force-ssl-{0}.conf" .format(wo_domain_name)): WOFileUtils.mvfile(self, "/etc/nginx/conf.d/force-ssl-{0}.conf" .format(wo_domain_name), "/etc/nginx/conf.d/force-ssl-{0}.conf.disabled" .format(wo_domain_name)) Log.info(self, "Disabled HTTPS Force Redirection for Site " " http://{0}".format(wo_domain_name)) def archivedCertificateHandle(self, domain): Log.warn(self, "You already have an existing certificate " "for the domain requested.\n" "(ref: {0}/" "{1}_ecc/{1}.conf)".format(WOVariables.wo_ssl_archive, domain) + "\nPlease select an option from below?" "\n\t1: Reinstall existing certificate" "\n\t2: Renew & replace the certificate (limit ~5 per 7 days)" "") check_prompt = input( "\nType the appropriate number [1-2] or any other key to cancel: ") if not os.path.isfile("{0}/{1}/fullchain.pem" .format(WOVariables.wo_ssl_live, domain)): Log.error( self, "{0}/{1}/fullchain.pem file is missing." .format(WOVariables.wo_ssl_live, domain)) if check_prompt == "1": Log.info(self, "Reinstalling SSL cert with acme.sh") ssl = WOShellExec.cmd_exec(self, "mkdir -p {0}/{1} && " "/etc/letsencrypt/acme.sh " "--config-home " "'/etc/letsencrypt/config' " "--install-cert -d {1} --ecc " "--cert-file {0}/{1}/cert.pem " "--key-file {0}/{1}/key.pem " "--fullchain-file " "{0}/{1}/fullchain.pem " "--ca-file {0}/{1}/ca.pem " "--reloadcmd " "\"nginx -t && service nginx restart\" " .format(WOVariables.wo_ssl_live, domain)) if ssl: try: if not os.path.isfile("/var/www/{0}/conf/nginx/ssl.conf" .format(domain)): Log.info( self, "Adding /var/www/{0}/conf/nginx/ssl.conf" .format(domain)) sslconf = open("/var/www/{0}/conf/nginx/ssl.conf" .format(domain), encoding='utf-8', mode='w') sslconf.write("listen 443 ssl http2;\n" "listen [::]:443 ssl http2;\n" "ssl_certificate " "{0}/{1}/fullchain.pem;\n" "ssl_certificate_key {0}/{1}/key.pem;\n" "ssl_trusted_certificate {0}/{1}/ca.pem;\n" "ssl_stapling_verify on;\n" .format(WOVariables.wo_ssl_live, domain)) sslconf.close() except IOError as e: Log.debug(self, str(e)) Log.debug(self, "Error occured while generating " "ssl.conf") elif (check_prompt == "2"): Log.info(self, "Issuing SSL cert with acme.sh") ssl = WOShellExec.cmd_exec(self, "/etc/letsencrypt/acme.sh " "--config-home " "'/etc/letsencrypt/config' " "--renew -d {0} --ecc " "--force" .format(domain)) if ssl: try: WOShellExec.cmd_exec(self, "mkdir -p {0}/{1} && " "/etc/letsencrypt/acme.sh " "--config-home " "'/etc/letsencrypt/config' " "--install-cert -d {1} --ecc " "--cert-file {0}/{1}/cert.pem " "--key-file {0}/{1}/key.pem " "--fullchain-file " "{0}/{1}/fullchain.pem " "ssl_trusted_certificate " "{0}/{1}/ca.pem;\n" "--reloadcmd " "\"nginx -t && service nginx restart\" " .format(WOVariables.wo_ssl_live, domain)) except IOError as e: Log.debug(self, str(e)) Log.debug(self, "Error occured while installing " "the certificate") else: Log.error(self, "Operation cancelled by user.") if os.path.isfile("{0}/conf/nginx/ssl.conf" .format(domain)): Log.info(self, "Existing ssl.conf . Backing it up ..") WOFileUtils.mvfile(self, "/var/www/{0}/conf/nginx/ssl.conf" .format(domain), '/var/www/{0}/conf/nginx/ssl.conf.bak' .format(domain)) return ssl