feat: convert WordOps from Nginx to OpenLiteSpeed + LSPHP + LSCache
Some checks failed
CI / test WordOps (ubuntu-22.04) (push) Has been cancelled
CI / test WordOps (ubuntu-24.04) (push) Has been cancelled

Complete conversion of the WordOps stack from Nginx + PHP-FPM to
OpenLiteSpeed + LSPHP + LSCache. This is a full rewrite across all 7
phases of the codebase:

- Foundation: OLS paths, variables, services, removed pynginxconfig dep
- Templates: 11 new OLS mustache templates, removed nginx-specific ones
- Stack: stack_pref, stack, stack_services, stack_upgrade, stack_migrate
- Site: site_functions, site, site_create, site_update
- Plugins: debug, info, log, clean rewritten for OLS
- SSL/ACME: acme.sh deploy uses lswsctrl, OLS vhssl blocks
- Other: secure, backup, clone, install script

Additional features:
- Debian 13 (trixie) support
- PHP 8.5 support
- WP Fort Knox mu-plugin integration (wo secure --lockdown/--unlock)
- --nginx CLI flag preserved for backward compatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 18:55:16 +01:00
parent aa127070e1
commit fa5bf17eb8
42 changed files with 2328 additions and 2926 deletions

View File

@@ -4,7 +4,8 @@ import subprocess
from cement.core.controller import CementBaseController, expose
from wo.cli.plugins.site_functions import (
check_domain_exists, deleteDB, deleteWebRoot, removeNginxConf, logwatch)
check_domain_exists, deleteDB, deleteWebRoot, removeOLSConf, logwatch,
addOLSListenerMap, removeOLSListenerMap)
from wo.cli.plugins.sitedb import (deleteSiteInfo, getAllsites,
getSiteInfo, updateSiteInfo)
from wo.cli.plugins.site_create import WOSiteCreateController
@@ -61,24 +62,20 @@ class WOSiteController(CementBaseController):
# 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)):
if os.path.isdir('{0}/{1}'
.format(WOVar.wo_ols_vhost_dir, 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"],
addOLSListenerMap(self, wo_domain)
WOGit.add(self, [WOVar.wo_ols_conf_dir],
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")
if not WOService.reload_service(self, 'lsws'):
Log.error(self, "service OpenLiteSpeed reload failed. "
"check issues with OpenLiteSpeed configuration")
else:
Log.error(self, 'nginx configuration file does not exist')
Log.error(self, 'OpenLiteSpeed vhost configuration does not exist')
@expose(help="Disable site example.com")
def disable(self):
@@ -98,28 +95,21 @@ class WOSiteController(CementBaseController):
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)):
if os.path.isdir('{0}/{1}'
.format(WOVar.wo_ols_vhost_dir, 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")
removeOLSListenerMap(self, wo_domain)
WOGit.add(self, [WOVar.wo_ols_conf_dir],
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, 'lsws'):
Log.error(self, "service OpenLiteSpeed reload failed. "
"check issues with OpenLiteSpeed configuration")
else:
Log.error(self, "nginx configuration file does not exist")
Log.error(self, "OpenLiteSpeed vhost configuration does not exist")
@expose(help="Get example.com information")
def info(self):
@@ -142,8 +132,8 @@ class WOSiteController(CementBaseController):
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)):
if os.path.isdir('{0}/{1}'
.format(WOVar.wo_ols_vhost_dir, wo_domain)):
siteinfo = getSiteInfo(self, wo_domain)
sitetype = siteinfo.site_type
cachetype = siteinfo.cache_type
@@ -175,7 +165,7 @@ class WOSiteController(CementBaseController):
"disabled"))
self.app.render((data), 'siteinfo.mustache')
else:
Log.error(self, "nginx configuration file does not exist")
Log.error(self, "OpenLiteSpeed vhost configuration does not exist")
@expose(help="Monitor example.com logs")
def log(self):
@@ -190,7 +180,7 @@ class WOSiteController(CementBaseController):
if logfiles:
logwatch(self, logfiles)
@expose(help="Display Nginx configuration of example.com")
@expose(help="Display OpenLiteSpeed configuration of example.com")
def show(self):
pargs = self.app.pargs
if not pargs.site_name:
@@ -208,17 +198,18 @@ class WOSiteController(CementBaseController):
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}"
if os.path.isdir('{0}/{1}'
.format(WOVar.wo_ols_vhost_dir, wo_domain)):
Log.info(self, "Display OpenLiteSpeed configuration for {0}"
.format(wo_domain))
f = open('/etc/nginx/sites-available/{0}'.format(wo_domain),
f = open('{0}/{1}/vhconf.conf'
.format(WOVar.wo_ols_vhost_dir, 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")
Log.error(self, "OpenLiteSpeed vhost configuration does not exist")
@expose(help="Change directory to site webroot")
def cd(self):
@@ -255,7 +246,7 @@ class WOSiteEditController(CementBaseController):
label = 'edit'
stacked_on = 'site'
stacked_type = 'nested'
description = ('Edit Nginx configuration of site')
description = ('Edit OpenLiteSpeed configuration of site')
arguments = [
(['site_name'],
dict(help='domain name for the site',
@@ -279,25 +270,28 @@ class WOSiteEditController(CementBaseController):
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)):
if os.path.isdir('{0}/{1}'
.format(WOVar.wo_ols_vhost_dir, wo_domain)):
try:
WOShellExec.invoke_editor(self, '/etc/nginx/sites-availa'
'ble/{0}'.format(wo_domain))
WOShellExec.invoke_editor(self, '{0}/{1}/vhconf.conf'
.format(WOVar.wo_ols_vhost_dir,
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}"
if (WOGit.checkfilestatus(self, WOVar.wo_ols_conf_dir,
'{0}/{1}/vhconf.conf'
.format(WOVar.wo_ols_vhost_dir,
wo_domain))):
WOGit.add(self, [WOVar.wo_ols_conf_dir],
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")
# Reload OpenLiteSpeed
if not WOService.reload_service(self, 'lsws'):
Log.error(self, "service OpenLiteSpeed reload failed. "
"check issues with OpenLiteSpeed configuration")
else:
Log.error(self, "nginx configuration file does not exists")
Log.error(self, "OpenLiteSpeed vhost configuration does not exist")
class WOSiteDeleteController(CementBaseController):
@@ -340,7 +334,7 @@ class WOSiteDeleteController(CementBaseController):
wo_domain = WODomain.validate(self, pargs.site_name)
wo_db_name = ''
wo_prompt = ''
wo_nginx_prompt = ''
wo_ols_prompt = ''
mark_db_delete_prompt = False
mark_webroot_delete_prompt = False
mark_db_deleted = False
@@ -430,8 +424,8 @@ class WOSiteDeleteController(CementBaseController):
if not pargs.force:
if (mark_webroot_deleted and mark_db_deleted):
# TODO Delete nginx conf
removeNginxConf(self, wo_domain)
# TODO Delete OLS conf
removeOLSConf(self, wo_domain)
deleteSiteInfo(self, wo_domain)
WOAcme.removeconf(self, wo_domain)
Log.info(self, "Deleted site {0}".format(wo_domain))
@@ -441,12 +435,13 @@ class WOSiteDeleteController(CementBaseController):
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)
# TODO Delete OLS conf
removeOLSConf(self, wo_domain)
deleteSiteInfo(self, wo_domain)
# To improve
if not WOFileUtils.grepcheck(
self, '/var/www/22222/conf/nginx/ssl.conf', wo_domain):
self, '{0}/22222/vhconf.conf'
.format(WOVar.wo_ols_vhost_dir), wo_domain):
WOAcme.removeconf(self, wo_domain)
Log.info(self, "Deleted site {0}".format(wo_domain))