- PHP 7.4 support - Improved Webp images support with Cloudflare (Issue [#95](https://github.com/WordOps/WordOps/issues/95)). Nginx will not serve webp images alternative with Cloudflare IP ranges. - Stack upgrade for adminer - Check acme.sh installation and setup acme.sh if needed before issuing certificate - Add `--ufw` to `wo stack status` - Add Nginx directive `gzip_static on;` to serve precompressed assets with Cache-Enabler or WP-Rocket. (Issue [#207](https://github.com/WordOps/WordOps/issues/207)) - Previous `--php73` & `--php73=off` flags are replaced by `--php72`, `--php73`, `--php74` to switch site's php version - phpMyAdmin updated to v4.9.2 - Adminer updated to v4.7.5 - Replace dot and dashes by underscores in database names (Issue [#206](https://github.com/WordOps/WordOps/issues/206)) - Increased database name length to 32 characters from domain name + 8 random characters - typo error in motd-news script (Issue [#204](https://github.com/WordOps/WordOps/issues/204)) - Install Nginx before ngxblocker - WordOps install/update script text color - Issue with MySQL stack on Raspbian 9/10 - Typo error (PR [#205](https://github.com/WordOps/WordOps/pull/205)) - php version in `wo debug` (PR [#209](https://github.com/WordOps/WordOps/pull/209)) - SSL certificates expiration display with shared wildcard certificates
491 lines
20 KiB
Python
491 lines
20 KiB
Python
import glob
|
|
import os
|
|
import subprocess
|
|
|
|
from cement.core.controller import CementBaseController, expose
|
|
from wo.cli.plugins.site_functions import *
|
|
from wo.cli.plugins.sitedb import (addNewSite, deleteSiteInfo, getAllsites,
|
|
getSiteInfo, updateSiteInfo)
|
|
from wo.cli.plugins.site_create import WOSiteCreateController
|
|
from wo.cli.plugins.site_update import WOSiteUpdateController
|
|
from wo.core.domainvalidate import WODomain
|
|
from wo.core.fileutils import WOFileUtils
|
|
from wo.core.git import WOGit
|
|
from wo.core.logging import Log
|
|
from wo.core.services import WOService
|
|
from wo.core.shellexec import WOShellExec, CommandExecutionError
|
|
from wo.core.sslutils import SSL
|
|
from wo.core.variables import WOVar
|
|
|
|
|
|
def wo_site_hook(app):
|
|
from wo.core.database import init_db
|
|
import wo.cli.plugins.models
|
|
init_db(app)
|
|
|
|
|
|
class WOSiteController(CementBaseController):
|
|
class Meta:
|
|
label = 'site'
|
|
stacked_on = 'base'
|
|
stacked_type = 'nested'
|
|
description = ('Performs website specific operations')
|
|
arguments = [
|
|
(['site_name'],
|
|
dict(help='Website name', nargs='?')),
|
|
]
|
|
usage = "wo site (command) <site_name> [options]"
|
|
|
|
@expose(hide=True)
|
|
def default(self):
|
|
self.app.args.print_help()
|
|
|
|
@expose(help="Enable site example.com")
|
|
def enable(self):
|
|
pargs = self.app.pargs
|
|
if not pargs.site_name:
|
|
try:
|
|
while not pargs.site_name:
|
|
pargs.site_name = (input('Enter site name : ')
|
|
.strip())
|
|
except IOError as e:
|
|
Log.debug(self, str(e))
|
|
Log.error(self, 'could not input site name')
|
|
|
|
pargs.site_name = pargs.site_name.strip()
|
|
# validate domain name
|
|
wo_domain = WODomain.validate(self, pargs.site_name)
|
|
|
|
# check if site exists
|
|
if not check_domain_exists(self, wo_domain):
|
|
Log.error(self, "site {0} does not exist".format(wo_domain))
|
|
if os.path.isfile('/etc/nginx/sites-available/{0}'
|
|
.format(wo_domain)):
|
|
Log.info(self, "Enable domain {0:10} \t".format(wo_domain), end='')
|
|
WOFileUtils.create_symlink(self,
|
|
['/etc/nginx/sites-available/{0}'
|
|
.format(wo_domain),
|
|
'/etc/nginx/sites-enabled/{0}'
|
|
.format(wo_domain)])
|
|
WOGit.add(self, ["/etc/nginx"],
|
|
msg="Enabled {0} "
|
|
.format(wo_domain))
|
|
updateSiteInfo(self, wo_domain, enabled=True)
|
|
Log.info(self, "[" + Log.ENDC + "OK" + Log.OKBLUE + "]")
|
|
if not WOService.reload_service(self, 'nginx'):
|
|
Log.error(self, "service nginx reload failed. "
|
|
"check issues with `nginx -t` command")
|
|
else:
|
|
Log.error(self, 'nginx configuration file does not exist')
|
|
|
|
@expose(help="Disable site example.com")
|
|
def disable(self):
|
|
pargs = self.app.pargs
|
|
if not pargs.site_name:
|
|
try:
|
|
while not pargs.site_name:
|
|
pargs.site_name = (input('Enter site name : ')
|
|
.strip())
|
|
|
|
except IOError as e:
|
|
Log.debug(self, str(e))
|
|
Log.error(self, 'could not input site name')
|
|
pargs.site_name = pargs.site_name.strip()
|
|
wo_domain = WODomain.validate(self, pargs.site_name)
|
|
# check if site exists
|
|
if not check_domain_exists(self, wo_domain):
|
|
Log.error(self, "site {0} does not exist".format(wo_domain))
|
|
|
|
if os.path.isfile('/etc/nginx/sites-available/{0}'
|
|
.format(wo_domain)):
|
|
Log.info(self, "Disable domain {0:10} \t"
|
|
.format(wo_domain), end='')
|
|
if not os.path.isfile('/etc/nginx/sites-enabled/{0}'
|
|
.format(wo_domain)):
|
|
Log.debug(self, "Site {0} already disabled".format(wo_domain))
|
|
Log.info(self, "[" + Log.FAIL + "Failed" + Log.OKBLUE + "]")
|
|
else:
|
|
WOFileUtils.remove_symlink(self,
|
|
'/etc/nginx/sites-enabled/{0}'
|
|
.format(wo_domain))
|
|
WOGit.add(self, ["/etc/nginx"],
|
|
msg="Disabled {0} "
|
|
.format(wo_domain))
|
|
updateSiteInfo(self, wo_domain, enabled=False)
|
|
Log.info(self, "[" + Log.ENDC + "OK" + Log.OKBLUE + "]")
|
|
if not WOService.reload_service(self, 'nginx'):
|
|
Log.error(self, "service nginx reload failed. "
|
|
"check issues with `nginx -t` command")
|
|
else:
|
|
Log.error(self, "nginx configuration file does not exist")
|
|
|
|
@expose(help="Get example.com information")
|
|
def info(self):
|
|
pargs = self.app.pargs
|
|
if not pargs.site_name:
|
|
try:
|
|
while not pargs.site_name:
|
|
pargs.site_name = (input('Enter site name : ')
|
|
.strip())
|
|
except IOError as e:
|
|
Log.debug(self, str(e))
|
|
Log.error(self, 'could not input site name')
|
|
pargs.site_name = pargs.site_name.strip()
|
|
wo_domain = WODomain.validate(self, pargs.site_name)
|
|
wo_www_domain = "www.{0}".format(wo_domain)
|
|
(wo_domain_type, wo_root_domain) = WODomain.getlevel(
|
|
self, wo_domain)
|
|
wo_db_name = ''
|
|
wo_db_user = ''
|
|
wo_db_pass = ''
|
|
|
|
if not check_domain_exists(self, wo_domain):
|
|
Log.error(self, "site {0} does not exist".format(wo_domain))
|
|
if os.path.isfile('/etc/nginx/sites-available/{0}'
|
|
.format(wo_domain)):
|
|
siteinfo = getSiteInfo(self, wo_domain)
|
|
sitetype = siteinfo.site_type
|
|
cachetype = siteinfo.cache_type
|
|
wo_site_webroot = siteinfo.site_path
|
|
access_log = (wo_site_webroot + '/logs/access.log')
|
|
error_log = (wo_site_webroot + '/logs/error.log')
|
|
wo_db_name = siteinfo.db_name
|
|
wo_db_user = siteinfo.db_user
|
|
wo_db_pass = siteinfo.db_password
|
|
wo_db_host = siteinfo.db_host
|
|
|
|
php_version = siteinfo.php_version
|
|
|
|
ssl = ("enabled" if siteinfo.is_ssl else "disabled")
|
|
if (ssl == "enabled"):
|
|
sslprovider = "Lets Encrypt"
|
|
sslexpiry = str(SSL.getexpirationdays(self, wo_domain))
|
|
else:
|
|
sslprovider = ''
|
|
sslexpiry = ''
|
|
data = dict(domain=wo_domain, webroot=wo_site_webroot,
|
|
accesslog=access_log, errorlog=error_log,
|
|
dbname=wo_db_name, dbuser=wo_db_user,
|
|
php_version=php_version,
|
|
dbpass=wo_db_pass,
|
|
ssl=ssl, sslprovider=sslprovider, sslexpiry=sslexpiry,
|
|
type=sitetype + " " + cachetype + " ({0})"
|
|
.format("enabled" if siteinfo.is_enabled else
|
|
"disabled"))
|
|
self.app.render((data), 'siteinfo.mustache')
|
|
else:
|
|
Log.error(self, "nginx configuration file does not exist")
|
|
|
|
@expose(help="Monitor example.com logs")
|
|
def log(self):
|
|
pargs = self.app.pargs
|
|
pargs.site_name = pargs.site_name.strip()
|
|
wo_domain = WODomain.validate(self, pargs.site_name)
|
|
wo_site_webroot = getSiteInfo(self, wo_domain).site_path
|
|
|
|
if not check_domain_exists(self, wo_domain):
|
|
Log.error(self, "site {0} does not exist".format(wo_domain))
|
|
logfiles = glob.glob(wo_site_webroot + '/logs/*.log')
|
|
if logfiles:
|
|
logwatch(self, logfiles)
|
|
|
|
@expose(help="Display Nginx configuration of example.com")
|
|
def show(self):
|
|
pargs = self.app.pargs
|
|
if not pargs.site_name:
|
|
try:
|
|
while not pargs.site_name:
|
|
pargs.site_name = (input('Enter site name : ')
|
|
.strip())
|
|
except IOError as e:
|
|
Log.debug(self, str(e))
|
|
Log.error(self, 'could not input site name')
|
|
# TODO Write code for wo site edit command here
|
|
pargs.site_name = pargs.site_name.strip()
|
|
wo_domain = WODomain.validate(self, pargs.site_name)
|
|
wo_www_domain = "www.{0}".format(wo_domain)
|
|
|
|
if not check_domain_exists(self, wo_domain):
|
|
Log.error(self, "site {0} does not exist".format(wo_domain))
|
|
|
|
if os.path.isfile('/etc/nginx/sites-available/{0}'
|
|
.format(wo_domain)):
|
|
Log.info(self, "Display NGINX configuration for {0}"
|
|
.format(wo_domain))
|
|
f = open('/etc/nginx/sites-available/{0}'.format(wo_domain),
|
|
encoding='utf-8', mode='r')
|
|
text = f.read()
|
|
Log.info(self, Log.ENDC + text)
|
|
f.close()
|
|
else:
|
|
Log.error(self, "nginx configuration file does not exists")
|
|
|
|
@expose(help="Change directory to site webroot")
|
|
def cd(self):
|
|
pargs = self.app.pargs
|
|
if not pargs.site_name:
|
|
try:
|
|
while not pargs.site_name:
|
|
pargs.site_name = (input('Enter site name : ')
|
|
.strip())
|
|
except IOError as e:
|
|
Log.debug(self, str(e))
|
|
Log.error(self, 'Unable to read input, please try again')
|
|
|
|
pargs.site_name = pargs.site_name.strip()
|
|
wo_domain = WODomain.validate(self, pargs.site_name)
|
|
|
|
if not check_domain_exists(self, wo_domain):
|
|
Log.error(self, "site {0} does not exist".format(wo_domain))
|
|
|
|
wo_site_webroot = getSiteInfo(self, wo_domain).site_path
|
|
if os.path.isdir(wo_site_webroot):
|
|
WOFileUtils.chdir(self, wo_site_webroot)
|
|
else:
|
|
Log.error(self, "unable to change directory")
|
|
|
|
|
|
class WOSiteEditController(CementBaseController):
|
|
class Meta:
|
|
label = 'edit'
|
|
stacked_on = 'site'
|
|
stacked_type = 'nested'
|
|
description = ('Edit Nginx configuration of site')
|
|
arguments = [
|
|
(['site_name'],
|
|
dict(help='domain name for the site',
|
|
nargs='?')),
|
|
]
|
|
|
|
@expose(hide=True)
|
|
def default(self):
|
|
pargs = self.app.pargs
|
|
if not pargs.site_name:
|
|
try:
|
|
while not pargs.site_name:
|
|
pargs.site_name = (input('Enter site name : ')
|
|
.strip())
|
|
except IOError as e:
|
|
Log.debug(self, str(e))
|
|
Log.error(self, 'Unable to read input, Please try again')
|
|
|
|
pargs.site_name = pargs.site_name.strip()
|
|
wo_domain = WODomain.validate(self, pargs.site_name)
|
|
wo_www_domain = "www.{0}".format(wo_domain)
|
|
if not check_domain_exists(self, wo_domain):
|
|
Log.error(self, "site {0} does not exist".format(wo_domain))
|
|
|
|
wo_site_webroot = WOVar.wo_webroot + wo_domain
|
|
|
|
if os.path.isfile('/etc/nginx/sites-available/{0}'
|
|
.format(wo_domain)):
|
|
try:
|
|
WOShellExec.invoke_editor(self, '/etc/nginx/sites-availa'
|
|
'ble/{0}'.format(wo_domain))
|
|
except CommandExecutionError as e:
|
|
Log.debug(self, str(e))
|
|
Log.error(self, "Failed invoke editor")
|
|
if (WOGit.checkfilestatus(self, "/etc/nginx",
|
|
'/etc/nginx/sites-available/{0}'
|
|
.format(wo_domain))):
|
|
WOGit.add(self, ["/etc/nginx"], msg="Edit website: {0}"
|
|
.format(wo_domain))
|
|
# Reload NGINX
|
|
if not WOService.reload_service(self, 'nginx'):
|
|
Log.error(self, "service nginx reload failed. "
|
|
"check issues with `nginx -t` command")
|
|
else:
|
|
Log.error(self, "nginx configuration file does not exists")
|
|
|
|
|
|
class WOSiteDeleteController(CementBaseController):
|
|
class Meta:
|
|
label = 'delete'
|
|
stacked_on = 'site'
|
|
stacked_type = 'nested'
|
|
description = 'delete an existing website'
|
|
arguments = [
|
|
(['site_name'],
|
|
dict(help='domain name to be deleted', nargs='?')),
|
|
(['--no-prompt'],
|
|
dict(help="doesnt ask permission for delete",
|
|
action='store_true')),
|
|
(['-f', '--force'],
|
|
dict(help="forcefully delete site and configuration",
|
|
action='store_true')),
|
|
(['--all'],
|
|
dict(help="delete files & db", action='store_true')),
|
|
(['--db'],
|
|
dict(help="delete db only", action='store_true')),
|
|
(['--files'],
|
|
dict(help="delete webroot only", action='store_true')),
|
|
]
|
|
|
|
@expose(help="Delete website configuration and files")
|
|
@expose(hide=True)
|
|
def default(self):
|
|
pargs = self.app.pargs
|
|
if not pargs.site_name and not pargs.all:
|
|
try:
|
|
while not pargs.site_name:
|
|
pargs.site_name = (input('Enter site name : ')
|
|
.strip())
|
|
except IOError as e:
|
|
Log.debug(self, str(e))
|
|
Log.error(self, 'could not input site name')
|
|
|
|
pargs.site_name = pargs.site_name.strip()
|
|
wo_domain = WODomain.validate(self, pargs.site_name)
|
|
wo_www_domain = "www.{0}".format(wo_domain)
|
|
wo_db_name = ''
|
|
wo_prompt = ''
|
|
wo_nginx_prompt = ''
|
|
mark_db_delete_prompt = False
|
|
mark_webroot_delete_prompt = False
|
|
mark_db_deleted = False
|
|
mark_webroot_deleted = False
|
|
if not check_domain_exists(self, wo_domain):
|
|
Log.error(self, "site {0} does not exist".format(wo_domain))
|
|
|
|
if ((not pargs.db) and (not pargs.files) and
|
|
(not pargs.all)):
|
|
pargs.all = True
|
|
|
|
if pargs.force:
|
|
pargs.no_prompt = True
|
|
|
|
# Gather information from wo-db for wo_domain
|
|
check_site = getSiteInfo(self, wo_domain)
|
|
wo_site_type = check_site.site_type
|
|
wo_site_webroot = check_site.site_path
|
|
if wo_site_webroot == 'deleted':
|
|
mark_webroot_deleted = True
|
|
if wo_site_type in ['mysql', 'wp', 'wpsubdir', 'wpsubdomain']:
|
|
wo_db_name = check_site.db_name
|
|
wo_db_user = check_site.db_user
|
|
if self.app.config.has_section('mysql'):
|
|
wo_mysql_grant_host = self.app.config.get(
|
|
'mysql', 'grant-host')
|
|
else:
|
|
wo_mysql_grant_host = 'localhost'
|
|
if wo_db_name == 'deleted':
|
|
mark_db_deleted = True
|
|
if pargs.all:
|
|
pargs.db = True
|
|
pargs.files = True
|
|
else:
|
|
if pargs.all:
|
|
mark_db_deleted = True
|
|
pargs.files = True
|
|
|
|
# Delete website database
|
|
if pargs.db:
|
|
if wo_db_name != 'deleted' and wo_db_name != '':
|
|
if not pargs.no_prompt:
|
|
wo_db_prompt = input('Are you sure, you want to delete'
|
|
' database [y/N]: ')
|
|
else:
|
|
wo_db_prompt = 'Y'
|
|
mark_db_delete_prompt = True
|
|
|
|
if wo_db_prompt == 'Y' or wo_db_prompt == 'y':
|
|
mark_db_delete_prompt = True
|
|
Log.info(self, "Deleting Database, {0}, user {1}"
|
|
.format(wo_db_name, wo_db_user))
|
|
deleteDB(self, wo_db_name, wo_db_user,
|
|
wo_mysql_grant_host, False)
|
|
updateSiteInfo(self, wo_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, "Does not seems to have database for this site."
|
|
)
|
|
|
|
# Delete webroot
|
|
if pargs.files:
|
|
if wo_site_webroot != 'deleted':
|
|
if not pargs.no_prompt:
|
|
wo_web_prompt = input('Are you sure, you want to delete '
|
|
'webroot [y/N]: ')
|
|
else:
|
|
wo_web_prompt = 'Y'
|
|
mark_webroot_delete_prompt = True
|
|
|
|
if wo_web_prompt == 'Y' or wo_web_prompt == 'y':
|
|
mark_webroot_delete_prompt = True
|
|
Log.info(self, "Deleting Webroot, {0}"
|
|
.format(wo_site_webroot))
|
|
deleteWebRoot(self, wo_site_webroot)
|
|
updateSiteInfo(self, wo_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 not pargs.force:
|
|
if (mark_webroot_deleted and mark_db_deleted):
|
|
# TODO Delete nginx conf
|
|
removeNginxConf(self, wo_domain)
|
|
deleteSiteInfo(self, wo_domain)
|
|
Log.info(self, "Deleted site {0}".format(wo_domain))
|
|
# else:
|
|
# Log.error(self, " site {0} does
|
|
# not exists".format(wo_domain))
|
|
else:
|
|
if (mark_db_delete_prompt or mark_webroot_delete_prompt or
|
|
(mark_webroot_deleted and mark_db_deleted)):
|
|
# TODO Delete nginx conf
|
|
removeNginxConf(self, wo_domain)
|
|
deleteSiteInfo(self, wo_domain)
|
|
Log.info(self, "Deleted site {0}".format(wo_domain))
|
|
|
|
|
|
class WOSiteListController(CementBaseController):
|
|
class Meta:
|
|
label = 'list'
|
|
stacked_on = 'site'
|
|
stacked_type = 'nested'
|
|
description = 'List websites'
|
|
arguments = [
|
|
(['--enabled'],
|
|
dict(help='List enabled websites', action='store_true')),
|
|
(['--disabled'],
|
|
dict(help="List disabled websites", action='store_true')),
|
|
]
|
|
|
|
@expose(help="Lists websites")
|
|
def default(self):
|
|
pargs = self.app.pargs
|
|
sites = getAllsites(self)
|
|
if not sites:
|
|
pass
|
|
|
|
if pargs.enabled:
|
|
for site in sites:
|
|
if site.is_enabled:
|
|
Log.info(self, "{0}".format(site.sitename))
|
|
elif pargs.disabled:
|
|
for site in sites:
|
|
if not site.is_enabled:
|
|
Log.info(self, "{0}".format(site.sitename))
|
|
else:
|
|
for site in sites:
|
|
Log.info(self, "{0}".format(site.sitename))
|
|
|
|
|
|
def load(app):
|
|
# register the plugin class.. this only happens if the plugin is enabled
|
|
app.handler.register(WOSiteController)
|
|
app.handler.register(WOSiteDeleteController)
|
|
app.handler.register(WOSiteUpdateController)
|
|
app.handler.register(WOSiteCreateController)
|
|
app.handler.register(WOSiteListController)
|
|
app.handler.register(WOSiteEditController)
|
|
# register a hook (function) to run after arguments are parsed.
|
|
app.hook.register('post_argument_parsing', wo_site_hook)
|