diff --git a/config/bash_completion.d/ee_auto.rc b/config/bash_completion.d/ee_auto.rc index 8f8608bd..a9d6b086 100644 --- a/config/bash_completion.d/ee_auto.rc +++ b/config/bash_completion.d/ee_auto.rc @@ -74,7 +74,7 @@ _ee_complete() # HANDLE EVERYTHING AFTER THE THIRD LEVEL NAMESPACE "install" | "purge" | "remove" ) COMPREPLY=( $(compgen \ - -W "--web --admin --mail --nginx --php --mysql --postfix --wpcli --phpmyadmin --adminer --utils --memcache --dovecot --all" \ + -W "--web --admin --mail --nginx --php --mysql --postfix --wpcli --phpmyadmin --adminer --utils --memcache --dovecot --all --mailscanner" \ -- $cur) ) ;; "start" | "stop" | "reload" | "restart" | "status") diff --git a/config/plugins.d/update.conf b/config/plugins.d/update.conf new file mode 100644 index 00000000..27e9c53a --- /dev/null +++ b/config/plugins.d/update.conf @@ -0,0 +1,8 @@ +### Example Plugin Configuration for EasyEngine + +[update] + +### If enabled, load a plugin named `example` either from the Python module +### `ee.cli.plugins.example` or from the file path +### `/var/lib/ee/plugins/example.py` +enable_plugin = true diff --git a/ee/cli/plugins/clean.py b/ee/cli/plugins/clean.py index b0f09e0d..9f1ca087 100644 --- a/ee/cli/plugins/clean.py +++ b/ee/cli/plugins/clean.py @@ -10,7 +10,7 @@ import os import urllib.request -def clean_plugin_hook(app): +def ee_clean_hook(app): # do something with the ``app`` object here. pass @@ -87,4 +87,4 @@ def load(app): # register the plugin class.. this only happens if the plugin is enabled handler.register(EECleanController) # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', clean_plugin_hook) + hook.register('post_argument_parsing', ee_clean_hook) diff --git a/ee/cli/plugins/debug.py b/ee/cli/plugins/debug.py index 557f5f11..0e9bab2f 100644 --- a/ee/cli/plugins/debug.py +++ b/ee/cli/plugins/debug.py @@ -15,7 +15,7 @@ import signal import subprocess -def debug_plugin_hook(app): +def ee_debug_hook(app): # do something with the ``app`` object here. pass @@ -551,4 +551,4 @@ def load(app): # register the plugin class.. this only happens if the plugin is enabled handler.register(EEDebugController) # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', debug_plugin_hook) + hook.register('post_argument_parsing', ee_debug_hook) diff --git a/ee/cli/plugins/import_slow_log.py b/ee/cli/plugins/import_slow_log.py index ba88fa08..4022d0e2 100644 --- a/ee/cli/plugins/import_slow_log.py +++ b/ee/cli/plugins/import_slow_log.py @@ -6,7 +6,7 @@ from ee.core.variables import EEVariables import os -def import_slow_log_plugin_hook(app): +def ee_import_slow_log_hook(app): pass @@ -68,4 +68,4 @@ def load(app): handler.register(EEImportslowlogController) # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', import_slow_log_plugin_hook) + hook.register('post_argument_parsing', ee_import_slow_log_hook) diff --git a/ee/cli/plugins/info.py b/ee/cli/plugins/info.py index e2b00d28..b8162cb3 100644 --- a/ee/cli/plugins/info.py +++ b/ee/cli/plugins/info.py @@ -10,7 +10,7 @@ import os import configparser -def info_plugin_hook(app): +def ee_info_hook(app): # do something with the ``app`` object here. pass @@ -200,4 +200,4 @@ def load(app): handler.register(EEInfoController) # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', info_plugin_hook) + hook.register('post_argument_parsing', ee_info_hook) diff --git a/ee/cli/plugins/log.py b/ee/cli/plugins/log.py index 20274c4c..e645d9fd 100644 --- a/ee/cli/plugins/log.py +++ b/ee/cli/plugins/log.py @@ -11,7 +11,7 @@ import os import glob -def log_plugin_hook(app): +def ee_log_hook(app): # do something with the ``app`` object here. pass @@ -138,4 +138,4 @@ def load(app): # register the plugin class.. this only happens if the plugin is enabled handler.register(EELogController) # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', log_plugin_hook) + hook.register('post_argument_parsing', ee_log_hook) diff --git a/ee/core/models.py b/ee/cli/plugins/models.py similarity index 74% rename from ee/core/models.py rename to ee/cli/plugins/models.py index 2592a26c..70a1aa1d 100644 --- a/ee/core/models.py +++ b/ee/cli/plugins/models.py @@ -7,6 +7,7 @@ class SiteDB(Base): Databse model for site table """ __tablename__ = 'sites' + __table_args__ = {'extend_existing': True} id = Column(Integer, primary_key=True) sitename = Column(String, unique=True) @@ -23,10 +24,15 @@ class SiteDB(Base): is_ssl = Column(Boolean, unique=False, default=False) storage_fs = Column(String) storage_db = Column(String) + db_name = Column(String) + db_user = Column(String) + db_password = Column(String) + db_host = Column(String) def __init__(self, sitename=None, site_type=None, cache_type=None, site_path=None, site_enabled=None, - is_ssl=None, storage_fs=None, storage_db=None): + is_ssl=None, storage_fs=None, storage_db=None, db_name=None, + db_user=None, db_password=None, db_host='localhost'): self.sitename = sitename self.site_type = site_type self.cache_type = cache_type @@ -35,6 +41,10 @@ class SiteDB(Base): self.is_ssl = is_ssl self.storage_fs = storage_fs self.storage_db = storage_db + self.db_name = db_name + self.db_user = db_user + self.db_password = db_password + self.db_host = db_host # def __repr__(self): # return '' % (self.site_type) diff --git a/ee/cli/plugins/secure.py b/ee/cli/plugins/secure.py index 3dddc9a7..02c20bb3 100644 --- a/ee/cli/plugins/secure.py +++ b/ee/cli/plugins/secure.py @@ -12,7 +12,7 @@ import hashlib import getpass -def secure_plugin_hook(app): +def ee_secure_hook(app): # do something with the ``app`` object here. pass @@ -139,4 +139,4 @@ def load(app): # register the plugin class.. this only happens if the plugin is enabled handler.register(EESecureController) # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', secure_plugin_hook) + hook.register('post_argument_parsing', ee_secure_hook) diff --git a/ee/cli/plugins/site.py b/ee/cli/plugins/site.py index cbf7e53b..b827171c 100644 --- a/ee/cli/plugins/site.py +++ b/ee/cli/plugins/site.py @@ -18,6 +18,7 @@ import subprocess def ee_site_hook(app): # do something with the ``app`` object here. from ee.core.database import init_db + import ee.cli.plugins.models init_db() @@ -523,7 +524,14 @@ class EESiteCreateController(CementBaseController): .format(ee_wp_creds['wp_pass']), log=False) display_cache_settings(self, data) - addNewSite(self, ee_domain, stype, cache, ee_site_webroot) + if 'ee_db_name' in data.keys(): + addNewSite(self, ee_domain, stype, cache, ee_site_webroot, + db_name=data['ee_db_name'], + db_user=data['ee_db_user'], + db_password=data['ee_db_pass'], + db_host=data['ee_db_host']) + else: + addNewSite(self, ee_domain, stype, cache, ee_site_webroot) Log.info(self, "Successfully created site" " http://{0}".format(ee_domain)) @@ -983,7 +991,14 @@ class EESiteUpdateController(CementBaseController): " {0}".format(ee_wp_creds['wp_user'])) Log.info(self, Log.ENDC + "WordPress admin password : {0}" .format(ee_wp_creds['wp_pass']) + "\n\n") - updateSiteInfo(self, ee_domain, stype=stype, cache=cache) + if oldsitetype in ['html', 'php'] and stype != 'php': + updateSiteInfo(self, ee_domain, stype=stype, cache=cache, + db_name=data['ee_db_name'], + db_user=data['ee_db_user'], + db_password=data['ee_db_pass'], + db_host=data['ee_db_host']) + else: + updateSiteInfo(self, ee_domain, stype=stype, cache=cache) Log.info(self, "Successfully updated site" " http://{0}".format(ee_domain)) @@ -1020,107 +1035,130 @@ class EESiteDeleteController(CementBaseController): ee_db_name = '' ee_prompt = '' ee_nginx_prompt = '' + mark_db_deleted = False + mark_webroot_deleted = False if ((not self.app.pargs.db) and (not self.app.pargs.files) and (not self.app.pargs.all)): self.app.pargs.all = True - if os.path.isfile('/etc/nginx/sites-available/{0}' - .format(ee_domain)): - ee_site_webroot = EEVariables.ee_webroot + ee_domain - - if self.app.pargs.no_prompt: - ee_prompt = 'Y' + # Gather information from ee-db for ee_domain + check_site = getSiteInfo(self, ee_domain) - if self.app.pargs.db: - if not ee_prompt: - ee_db_prompt = input('Do you want to delete database' - '[Y/N]: ') + if check_site is None: + Log.error(self, " Site {0} does not exist.".format(ee_domain)) + else: + ee_site_type = check_site.site_type + ee_site_webroot = check_site.site_path + if ee_site_webroot == 'deleted': + mark_webroot_deleted = True + if ee_site_type in ['mysql', 'wp', 'wpsubdir', 'wpsubdomain']: + ee_db_name = check_site.db_name + ee_db_user = check_site.db_user + ee_db_host = check_site.db_host + if ee_db_name == 'deleted': + mark_db_deleted = True + if self.app.pargs.all: + self.app.pargs.db = True + self.app.pargs.files = True + else: + if self.app.pargs.all: + mark_db_deleted = True + self.app.pargs.files = True + + # Delete website database + if self.app.pargs.db: + if ee_db_name != 'deleted': + if not self.app.pargs.no_prompt: + ee_db_prompt = input('Are you sure, you want to delete' + ' database [y/N]: ') else: ee_db_prompt = 'Y' if ee_db_prompt == 'Y' or ee_db_prompt == 'y': - self.deleteDB(ee_site_webroot) - - if self.app.pargs.files: - if not ee_prompt: - ee_web_prompt = input('Do you want to delete webroot' - '[Y/N]: ') - else: - ee_web_prompt = 'Y' - - if ee_web_prompt == 'Y' or ee_web_prompt == 'y': - self.deleteWebRoot(ee_site_webroot) - - if self.app.pargs.all: - if not ee_prompt: - ee_db_prompt = input('Do you want to delete database' - '[Y/N]: ' - ) - ee_web_prompt = input('Do you want to delete webroot' - '[Y/N]: ') - ee_nginx_prompt = input('Do you want to delete NGINX' - ' configuration [Y/N]: ') + Log.info(self, "Deleting Database, {0}, user {1}" + .format(ee_db_name, ee_db_user)) + self.deleteDB(ee_db_name, ee_db_user, ee_db_host) + updateSiteInfo(self, ee_domain, + db_name='deleted', + db_user='deleted', + db_password='deleted') + mark_db_deleted = True + Log.info(self, "Deleted Database successfully.") + else: + mark_db_deleted = True + Log.info(self, "Database seems to be deleted.") + + # Delete webroot + if self.app.pargs.files: + if ee_site_webroot != 'deleted': + if not self.app.pargs.no_prompt: + ee_web_prompt = input('Are you sure, you want to delete ' + 'webroot [y/N]: ') else: - ee_db_prompt = 'Y' ee_web_prompt = 'Y' - ee_nginx_prompt = 'Y' - if ee_db_prompt == 'Y' or ee_db_prompt == 'y': - self.deleteDB(ee_site_webroot) if ee_web_prompt == 'Y' or ee_web_prompt == 'y': + Log.info(self, "Deleting Webroot, {0}" + .format(ee_site_webroot)) self.deleteWebRoot(ee_site_webroot) + updateSiteInfo(self, ee_domain, webroot='deleted') + mark_webroot_deleted = True + Log.info(self, "Deleted webroot successfully") + else: + mark_webroot_deleted = True + Log.info(self, "Webroot seems to be already deleted") - if (ee_nginx_prompt == 'Y' or ee_nginx_prompt == 'y'): - Log.debug(self, "Removing Nginx configuration") - EEFileUtils.rm(self, '/etc/nginx/sites-enabled/{0}' - .format(ee_domain)) - EEFileUtils.rm(self, '/etc/nginx/sites-available/{0}' - .format(ee_domain)) - EEGit.add(self, ["/etc/nginx"], - msg="Deleted {0} " - .format(ee_domain)) + if (mark_webroot_deleted and mark_db_deleted): + # TODO Delete nginx conf + self.removeNginxConf(ee_domain) deleteSiteInfo(self, ee_domain) - - Log.info(self, "Deleted site {0}".format(ee_domain)) - else: - Log.error(self, " site {0} does not exists".format(ee_domain)) + Log.info(self, "Deleted site {0}".format(ee_domain)) + # else: + # Log.error(self, " site {0} does not exists".format(ee_domain)) @expose(hide=True) - def deleteDB(self, webroot): - configfiles = glob.glob(webroot + '/*-config.php') - if configfiles: - if EEFileUtils.isexist(self, configfiles[0]): - ee_db_name = (EEFileUtils.grep(self, configfiles[0], - 'DB_NAME').split(',')[1] - .split(')')[0].strip().replace('\'', '')) - ee_db_user = (EEFileUtils.grep(self, configfiles[0], - 'DB_USER').split(',')[1] - .split(')')[0].strip().replace('\'', '')) - ee_db_pass = (EEFileUtils.grep(self, configfiles[0], - 'DB_PASSWORD').split(',')[1] - .split(')')[0].strip().replace('\'', '')) - ee_db_host = (EEFileUtils.grep(self, configfiles[0], - 'DB_HOST').split(',')[1] - .split(')')[0].strip().replace('\'', '')) - try: - Log.debug(self, "dropping database `{0}`".format(ee_db_name)) + def deleteDB(self, dbname, dbuser, dbhost): + try: + # Check if Database exists + + # Drop database if exists + Log.debug(self, "dropping database `{0}`".format(dbname)) + EEMysql.execute(self, + "drop database `{0}`".format(dbname), + errormsg='Unable to drop database {0}' + .format(dbname)) + + if dbuser != 'root': + Log.debug(self, "dropping user `{0}`".format(dbuser)) EEMysql.execute(self, - "drop database `{0}`".format(ee_db_name), - errormsg='Unable to drop database {0}' - .format(ee_db_name)) - if ee_db_user != 'root': - Log.debug(self, "dropping user `{0}`".format(ee_db_user)) - EEMysql.execute(self, - "drop user `{0}`@`{1}`" - .format(ee_db_user, ee_db_host)) - EEMysql.execute(self, - "flush privileges") - except Exception as e: - Log.error(self, "Error occured while deleting database") + "drop user `{0}`@`{1}`" + .format(dbuser, dbhost)) + EEMysql.execute(self, "flush privileges") + except Exception as e: + Log.error(self, "Error occured while deleting database") @expose(hide=True) def deleteWebRoot(self, webroot): - EEFileUtils.rm(self, webroot) + if os.path.isdir(webroot): + Log.debug(self, "Removing {0}".format(webroot)) + EEFileUtils.rm(self, webroot) + return True + else: + Log.debug(self, "{0} does not exist".format(webroot)) + return False + + @expose(hide=True) + def removeNginxConf(self, domain): + if os.path.isfile('/etc/nginx/sites-available/{0}' + .format(domain)): + Log.debug(self, "Removing Nginx configuration") + EEFileUtils.rm(self, '/etc/nginx/sites-enabled/{0}' + .format(domain)) + EEFileUtils.rm(self, '/etc/nginx/sites-available/{0}' + .format(domain)) + EEGit.add(self, ["/etc/nginx"], + msg="Deleted {0} " + .format(domain)) class EESiteListController(CementBaseController): diff --git a/ee/cli/plugins/site_functions.py b/ee/cli/plugins/site_functions.py index d7dcd6fe..f6c4eae4 100644 --- a/ee/cli/plugins/site_functions.py +++ b/ee/cli/plugins/site_functions.py @@ -106,12 +106,17 @@ def setupdatabase(self, data): if len(ee_db_username) > 16: Log.debug(self, 'Autofix MySQL username (ERROR 1470 (HY000)),' ' please wait') - ee_random10 = (''.join(random.sample(string.ascii_uppercase + - string.ascii_lowercase + string.digits, 10))) - ee_db_username = (ee_db_name[0:6] + ee_random10) + ee_db_username = (ee_db_name[0:6] + generate_random()) + # create MySQL database Log.info(self, "Setting up database\t\t", end='') Log.debug(self, "Creating databse {0}".format(ee_db_name)) + + if EEMysql.check_db_exists(self, ee_db_name): + Log.debug(self, "Database already exists, Updating DB_NAME .. ") + ee_db_name = (ee_db_name[0:6] + generate_random()) + ee_db_username = (ee_db_name[0:6] + generate_random()) + EEMysql.execute(self, "create database `{0}`" .format(ee_db_name), errormsg="Cannot create database") @@ -359,7 +364,8 @@ def setupwordpressnetwork(self, data): def installwp_plugin(self, plugin_name, data): ee_site_webroot = data['webroot'] - Log.info(self, "Installing plugin {0}".format(plugin_name)) + Log.info(self, "Installing plugin {0}, please wait ..." + .format(plugin_name)) EEFileUtils.chdir(self, '{0}/htdocs/'.format(ee_site_webroot)) EEShellExec.cmd_exec(self, "php /usr/bin/wp plugin --allow-root install " "{0}".format(plugin_name), @@ -376,7 +382,8 @@ def installwp_plugin(self, plugin_name, data): def uninstallwp_plugin(self, plugin_name, data): ee_site_webroot = data['webroot'] - Log.debug(self, "Uninstalling plugin {0}".format(plugin_name)) + Log.debug(self, "Uninstalling plugin {0}, please wait ..." + .format(plugin_name)) EEFileUtils.chdir(self, '{0}/htdocs/'.format(ee_site_webroot)) EEShellExec.cmd_exec(self, "php /usr/bin/wp plugin --allow-root uninstall " "{0}".format(plugin_name), @@ -581,3 +588,9 @@ def logwatch(self, logfiles): l = logwatch.LogWatcher(logfiles, callback) l.loop() + + +def generate_random(): + ee_random10 = (''.join(random.sample(string.ascii_uppercase + + string.ascii_lowercase + string.digits, 10))) + return ee_random10 diff --git a/ee/cli/plugins/sitedb.py b/ee/cli/plugins/sitedb.py index 805b72bc..e177544d 100644 --- a/ee/cli/plugins/sitedb.py +++ b/ee/cli/plugins/sitedb.py @@ -4,17 +4,20 @@ from sqlalchemy.orm import relationship, backref from sqlalchemy.ext.declarative import declarative_base from ee.core.logging import Log from ee.core.database import db_session -from ee.core.models import SiteDB +from ee.cli.plugins.models import SiteDB import sys def addNewSite(self, site, stype, cache, path, - enabled=True, ssl=False, fs='ext4', db='mysql'): + enabled=True, ssl=False, fs='ext4', db='mysql', + db_name=None, db_user=None, db_password=None, + db_host='localhost'): """ Add New Site record information into ee database. """ try: - newRec = SiteDB(site, stype, cache, path, enabled, ssl, fs, db) + newRec = SiteDB(site, stype, cache, path, enabled, ssl, fs, db, + db_name, db_user, db_password, db_host) db_session.add(newRec) db_session.commit() except Exception as e: @@ -34,8 +37,9 @@ def getSiteInfo(self, site): Log.error(self, "Unable to query database for site info") -def updateSiteInfo(self, site, stype='', cache='', - enabled=True, ssl=False, fs='', db=''): +def updateSiteInfo(self, site, stype='', cache='', webroot='', + enabled=True, ssl=False, fs='', db='', db_name=None, + db_user=None, db_password=None, db_host=None): """updates site record in database""" try: q = SiteDB.query.filter(SiteDB.sitename == site).first() @@ -59,6 +63,21 @@ def updateSiteInfo(self, site, stype='', cache='', if ssl and q.is_ssl != ssl: q.is_ssl = ssl + if db_name and q.db_name != db_name: + q.db_name = db_name + + if db_user and q.db_user != db_user: + q.db_user = db_user + + if db_user and q.db_password != db_password: + q.db_password = db_password + + if db_host and q.db_host != db_host: + q.db_host = db_host + + if webroot and q.site_path != webroot: + q.site_path = webroot + try: q.created_on = func.now() db_session.commit() diff --git a/ee/cli/plugins/stack.py b/ee/cli/plugins/stack.py index 96d34716..079ec469 100644 --- a/ee/cli/plugins/stack.py +++ b/ee/cli/plugins/stack.py @@ -514,7 +514,8 @@ class EEStackController(CementBaseController): # config.write(configfile) if not os.path.isfile("/etc/mysql/my.cnf"): config = ("[mysqld]\nwait_timeout = 30\n" - "interactive_timeout=60\nperformance_schema = 0") + "interactive_timeout=60\nperformance_schema = 0" + "\nquery_cache_type = 1") config_file = open("/etc/mysql/my.cnf", encoding='utf-8', mode='w') config_file.write(config) @@ -523,7 +524,8 @@ class EEStackController(CementBaseController): EEShellExec.cmd_exec(self, "sed -i \"/#max_connections/a " "wait_timeout = 30 \\n" "interactive_timeout = 60 \\n" - "performance_schema = 0\" " + "performance_schema = 0\\n" + "query_cache_type = 1 \" " "/etc/mysql/my.cnf") EEGit.add(self, ["/etc/mysql"], msg="Adding MySQL into Git") diff --git a/ee/cli/plugins/update.py b/ee/cli/plugins/update.py new file mode 100644 index 00000000..517abca2 --- /dev/null +++ b/ee/cli/plugins/update.py @@ -0,0 +1,40 @@ +from cement.core.controller import CementBaseController, expose +from cement.core import handler, hook +from ee.core.download import EEDownload +import time +import os + + +def ee_update_hook(app): + # do something with the ``app`` object here. + pass + + +class EEUpdateController(CementBaseController): + class Meta: + label = 'ee_update' + stacked_on = 'base' + aliases = ['update'] + aliases_only = True + stacked_type = 'nested' + description = ('update EasyEngine') + usage = "ee update" + + @expose(hide=True) + def default(self): + filename = "eeupdate" + time.strftime("%Y%m%d-%H%M%S") + EEDownload.download(self, [["http://rt.cx/ee", + "/tmp/{0}".format(filename), + "EasyEngine update script"]]) + try: + os.system("bash /tmp/{0}".format(filename)) + except Exception as e: + Log.debug(self, e) + Log.error(self, "EasyEngine update failed !") + + +def load(app): + # register the plugin class.. this only happens if the plugin is enabled + handler.register(EEUpdateController) + # register a hook (function) to run after arguments are parsed. + hook.register('post_argument_parsing', ee_update_hook) diff --git a/ee/core/database.py b/ee/core/database.py index 573ee099..888dca38 100644 --- a/ee/core/database.py +++ b/ee/core/database.py @@ -19,6 +19,6 @@ def init_db(): """ # import all modules here that might define models so that # they will be registered properly on the metadata. Otherwise - # you will have to import them first before calling init_db() - import ee.core.models + # # you will have to import them first before calling init_db() + # import ee.core.models Base.metadata.create_all(bind=engine) diff --git a/ee/core/mysql.py b/ee/core/mysql.py index 7c523bf1..8295c770 100644 --- a/ee/core/mysql.py +++ b/ee/core/mysql.py @@ -1,5 +1,6 @@ """EasyEngine MySQL core classes.""" import pymysql +from pymysql import connections, DatabaseError import configparser from os.path import expanduser import sys @@ -49,9 +50,10 @@ class EEMysql(): Log.debug(self, "Executing MySQL statement: {0}" .format(statement)) - cur.execute(statement) + result = cur.execute(statement) cur.close() conn.close() + return result except Exception as e: cur.close() @@ -101,3 +103,17 @@ class EEMysql(): except Exception as e: Log.error(self, "Error: process exited with status %s" % e) + + def check_db_exists(self, db_name): + try: + connection = connections.Connection(db=db_name, + read_default_file='~/.my.cnf') + if connection: + return True + except DatabaseError as e: + if e.args[1] == '#42000Unknown database \'{0}\''.format(db_name): + return False + else: + raise RuntimeError + except Exception as e: + Log.error(self, "Runtime Exception occured")