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

@@ -9,7 +9,7 @@ from wo.cli.plugins.site_functions import (
pre_run_checks, setupdomain, SiteError,
setupdatabase, setupwordpress, setwebrootpermissions,
display_cache_settings, copyWildcardCert,
updatewpuserpassword, setupngxblocker, setupwp_plugin,
updatewpuserpassword, setupwp_plugin,
setupwordpressnetwork, installwp_plugin, sitebackup, uninstallwp_plugin)
from wo.cli.plugins.sitedb import (getAllsites,
getSiteInfo, updateSiteInfo)
@@ -88,11 +88,6 @@ class WOSiteUpdateController(CementBaseController):
action='store' or 'store_const',
choices=('on', 'off'),
const='on', nargs='?')),
(['--ngxblocker'],
dict(help="enable Ultimate Nginx bad bot blocker",
action='store' or 'store_const',
choices=('on', 'off'),
const='on', nargs='?')),
(['--proxy'],
dict(help="update to proxy site", nargs='+')),
(['--all'],
@@ -195,10 +190,11 @@ class WOSiteUpdateController(CementBaseController):
check_php_version = check_site.php_version
if ((pargs.password or pargs.hsts or
pargs.ngxblocker or pargs.letsencrypt == 'renew') and not (
pargs.letsencrypt == 'renew') and not (
pargs.html or pargs.php or pargs.php74 or pargs.php80 or
pargs.php81 or pargs.php82 or
pargs.php83 or pargs.php84 or pargs.mysql or pargs.wp or pargs.wpfc or pargs.wpsc or
pargs.php83 or pargs.php84 or pargs.php85 or
pargs.mysql or pargs.wp or pargs.wpfc or pargs.wpsc or
pargs.wprocket or pargs.wpce or
pargs.wpsubdir or pargs.wpsubdomain)):
@@ -217,38 +213,13 @@ class WOSiteUpdateController(CementBaseController):
SSL.setuphsts(self, wo_domain, enable=True)
elif pargs.hsts == "off":
SSL.setuphsts(self, wo_domain, enable=False)
# Service Nginx Reload
if not WOService.reload_service(self, 'nginx'):
# Service OLS Reload
if not WOService.reload_service(self, 'lsws'):
Log.error(
self, "service nginx reload failed. "
"check issues with `nginx -t` command")
self, "service lsws reload failed. "
"check issues with OpenLiteSpeed config")
else:
return 0
# setup ngxblocker
if (pargs.ngxblocker):
if pargs.ngxblocker == "on":
if os.path.isdir('/etc/nginx/bots.d'):
try:
setupngxblocker(self, wo_domain)
except SiteError as e:
Log.debug(self, str(e))
Log.info(self, "\nngxblocker not enabled.")
else:
Log.error(self, 'ngxblocker stack is not installed')
elif pargs.ngxblocker == "off":
try:
setupngxblocker(self, wo_domain, False)
except SiteError as e:
Log.debug(self, str(e))
Log.info(self, "\nngxblocker not enabled.")
# Service Nginx Reload
if not WOService.reload_service(self, 'nginx'):
Log.error(self, "service nginx reload failed. "
"check issues with `nginx -t` command")
else:
return 0
# letsencryot rebew
if (pargs.letsencrypt == 'renew'):
if WOAcme.cert_check(self, wo_domain):
@@ -268,19 +239,22 @@ class WOSiteUpdateController(CementBaseController):
if (((stype == 'php' and
oldsitetype not in ['html', 'proxy', 'php', 'php74', 'php80',
'php81', 'php82', 'php83', 'php84']) or
'php81', 'php82', 'php83', 'php84',
'php85']) or
(stype == 'mysql' and oldsitetype not in [
'html', 'php', 'php74', 'php80', 'php81',
'php82', 'php83', 'php84', 'proxy']) or
'php82', 'php83', 'php84', 'php85', 'proxy']) or
(stype == 'wp' and oldsitetype not in [
'html', 'php', 'php74', 'php80', 'php81',
'php82', 'php83', 'php84', 'mysql', 'proxy', 'wp']) or
'php82', 'php83', 'php84', 'php85',
'mysql', 'proxy', 'wp']) or
(stype == 'wpsubdir' and oldsitetype in ['wpsubdomain']) or
(stype == 'wpsubdomain' and oldsitetype in ['wpsubdir']) or
(stype == oldsitetype and cache == oldcachetype)) and
not (pargs.php74 or pargs.php80 or
pargs.php81 or pargs.php82 or
pargs.php83 or pargs.php84 or pargs.alias)):
pargs.php83 or pargs.php84 or
pargs.php85 or pargs.alias)):
Log.info(self, Log.FAIL + "can not update {0} {1} to {2} {3}".
format(oldsitetype, oldcachetype, stype, cache))
return 1
@@ -338,7 +312,7 @@ class WOSiteUpdateController(CementBaseController):
site_name=wo_domain, www_domain=wo_www_domain,
static=False, basic=True, wp=False, wpfc=False,
php74=False, php80=False, php81=False, php82=False, php83=False,
php84=False, wpsc=False, wpredis=False, wprocket=False, wpce=False,
php84=False, php85=False, wpsc=False, wpredis=False, wprocket=False, wpce=False,
multisite=False, wpsubdir=False, webroot=wo_site_webroot,
currsitetype=oldsitetype, currcachetype=oldcachetype)
@@ -362,8 +336,8 @@ class WOSiteUpdateController(CementBaseController):
data['wpsubdir'] = True
if ((pargs.php74 or pargs.php80 or pargs.php81 or
pargs.php82 or pargs.php83 or pargs.php84) and
(not data)):
pargs.php82 or pargs.php83 or pargs.php84 or
pargs.php85) and (not data)):
Log.debug(
self, "pargs php74, "
"or php80, or php81 or php82 or php83 or php84 enabled")
@@ -384,7 +358,7 @@ class WOSiteUpdateController(CementBaseController):
oldsitetype == 'php73' or oldsitetype == 'php74' or
oldsitetype == 'php80' or oldsitetype == 'php81' or
oldsitetype == 'php82' or oldsitetype == 'php83' or
oldsitetype == 'php84'):
oldsitetype == 'php84' or oldsitetype == 'php85'):
data['static'] = False
data['wp'] = False
data['multisite'] = False
@@ -436,7 +410,8 @@ class WOSiteUpdateController(CementBaseController):
if (data and (not pargs.php74) and
(not pargs.php80) and (not pargs.php81) and (not pargs.php82)
and (not pargs.php83) and (not pargs.php84)):
and (not pargs.php83) and (not pargs.php84)
and (not pargs.php85)):
data[pargs_version] = bool(old_version_var is True)
Log.debug(
self, f"data {pargs_version} = {data[pargs_version]}")
@@ -546,19 +521,19 @@ class WOSiteUpdateController(CementBaseController):
data['wo_db_pass'] = check_site.db_password
data['wo_db_host'] = check_site.db_host
if not (pargs.letsencrypt or pargs.hsts or pargs.ngxblocker):
if not (pargs.letsencrypt or pargs.hsts):
try:
pre_run_checks(self)
except SiteError as e:
Log.debug(self, str(e))
Log.error(self, "NGINX configuration check failed.")
Log.error(self, "OpenLiteSpeed configuration check failed.")
try:
sitebackup(self, data)
except Exception as e:
Log.debug(self, str(e))
# setup NGINX configuration, and webroot
# setup OpenLiteSpeed configuration, and webroot
try:
setupdomain(self, data)
except SiteError as e:
@@ -677,9 +652,9 @@ class WOSiteUpdateController(CementBaseController):
self, wo_domain, acme_domains, redirect=True)
SSL.siteurlhttps(self, wo_domain)
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 lsws reload failed. "
"check issues with OpenLiteSpeed config")
Log.info(self, "Congratulations! Successfully "
"Configured SSL on https://{0}".format(wo_domain))
letsencrypt = True
@@ -694,29 +669,29 @@ class WOSiteUpdateController(CementBaseController):
elif data['letsencrypt'] is False:
if pargs.letsencrypt == "off":
if os.path.islink("{0}/conf/nginx/ssl.conf"
.format(wo_site_webroot)):
if os.path.islink("{0}/{1}/ssl.conf"
.format(WOVar.wo_ols_vhost_dir, wo_domain)):
WOFileUtils.remove_symlink(self,
"{0}/conf/nginx/ssl.conf"
.format(wo_site_webroot))
elif os.path.isfile("{0}/conf/nginx/ssl.conf"
.format(wo_site_webroot)):
Log.info(self, 'Setting Nginx configuration')
WOFileUtils.mvfile(self, "{0}/conf/nginx/ssl.conf"
.format(wo_site_webroot),
'{0}/conf/nginx/ssl.conf.disabled'
.format(wo_site_webroot))
"{0}/{1}/ssl.conf"
.format(WOVar.wo_ols_vhost_dir, wo_domain))
elif os.path.isfile("{0}/{1}/ssl.conf"
.format(WOVar.wo_ols_vhost_dir, wo_domain)):
Log.info(self, 'Setting OpenLiteSpeed configuration')
WOFileUtils.mvfile(self, "{0}/{1}/ssl.conf"
.format(WOVar.wo_ols_vhost_dir, wo_domain),
'{0}/{1}/ssl.conf.disabled'
.format(WOVar.wo_ols_vhost_dir, wo_domain))
SSL.httpsredirect(
self, wo_domain, acmedata, redirect=False)
if os.path.isfile("{0}/conf/nginx/hsts.conf"
.format(wo_site_webroot)):
WOFileUtils.mvfile(self, "{0}/conf/nginx/hsts.conf"
.format(wo_site_webroot),
'{0}/conf/nginx/'
if os.path.isfile("{0}/{1}/hsts.conf"
.format(WOVar.wo_ols_vhost_dir, wo_domain)):
WOFileUtils.mvfile(self, "{0}/{1}/hsts.conf"
.format(WOVar.wo_ols_vhost_dir, wo_domain),
'{0}/{1}/'
'hsts.conf.disabled'
.format(wo_site_webroot))
.format(WOVar.wo_ols_vhost_dir, wo_domain))
# find all broken symlinks
sympath = (f'{wo_site_webroot}/conf')
sympath = ('{0}/{1}'.format(WOVar.wo_ols_vhost_dir, wo_domain))
WOFileUtils.findBrokenSymlink(self, sympath)
elif (pargs.letsencrypt == "clean" or
@@ -730,9 +705,9 @@ class WOSiteUpdateController(CementBaseController):
sympath = "{0}/conf".format(site.site_path)
WOFileUtils.findBrokenSymlink(self, sympath)
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 lsws reload failed. "
"check issues with OpenLiteSpeed config")
# Log.info(self,"Removing Cron Job set for cert
# auto-renewal") WOCron.remove_cron(self,'wo site
# update {0} --le=renew --min_expiry_limit 30
@@ -741,8 +716,8 @@ class WOSiteUpdateController(CementBaseController):
" http://{0}".format(wo_domain))
letsencrypt = False
# Add nginx conf folder into GIT
WOGit.add(self, ["{0}/conf/nginx".format(wo_site_webroot)],
# Add OLS conf folder into GIT
WOGit.add(self, ["{0}/{1}".format(WOVar.wo_ols_vhost_dir, wo_domain)],
msg="Adding letsencrypts config of site: {0}"
.format(wo_domain))
updateSiteInfo(self, wo_domain, ssl=letsencrypt)
@@ -750,10 +725,10 @@ class WOSiteUpdateController(CementBaseController):
if stype == oldsitetype and cache == oldcachetype:
# Service Nginx Reload
if not WOService.reload_service(self, 'nginx'):
Log.error(self, "service nginx reload failed. "
"check issues with `nginx -t` command")
# Service OLS Reload
if not WOService.reload_service(self, 'lsws'):
Log.error(self, "service lsws reload failed. "
"check issues with OpenLiteSpeed config")
updateSiteInfo(self, wo_domain, stype=stype, cache=cache,
ssl=(bool(check_site.is_ssl)),
@@ -796,7 +771,8 @@ class WOSiteUpdateController(CementBaseController):
# Setup WordPress if old sites are html/php/mysql sites
if data['wp'] and oldsitetype in ['html', 'proxy', 'php', 'php72',
'mysql', 'php73', 'php74', 'php80',
'php81', 'php82', 'php83', 'php84']:
'php81', 'php82', 'php83', 'php84',
'php85']:
try:
wo_wp_creds = setupwordpress(self, data)
except SiteError as e:
@@ -827,35 +803,27 @@ class WOSiteUpdateController(CementBaseController):
data['multisite'] and data['wpfc'])):
try:
plugin_data_object = {
"log_level": "INFO",
"log_filesize": 5,
"enable_purge": 1,
"enable_map": "0",
"enable_log": 0,
"enable_stamp": 1,
"purge_homepage_on_new": 1,
"purge_homepage_on_edit": 1,
"purge_homepage_on_del": 1,
"purge_archive_on_new": 1,
"purge_archive_on_edit": 0,
"purge_archive_on_del": 0,
"purge_archive_on_new_comment": 0,
"purge_archive_on_deleted_comment": 0,
"purge_page_on_mod": 1,
"purge_page_on_new_comment": 1,
"purge_page_on_deleted_comment": 1,
"cache_method": "enable_fastcgi",
"purge_method": "get_request",
"redis_hostname": "127.0.0.1",
"redis_port": "6379",
"redis_prefix": "nginx-cache:"}
"cache-lscwp": "on",
"cache-pub_priv_ttl": 604800,
"cache-commenter": "on",
"cache-rest": "on",
"cache-page_login": "on",
"cache-favicon": "on",
"cache-resources": "on",
"cache-mobile": "on",
"cache-nocache_cookies": "",
"cache-nocache_useragents": "",
"purge-purge_on_upgrade": "on",
"purge-auto_purge": "on",
"purge-stale": "on",
"purge-hook_all": "on"}
plugin_data = json.dumps(plugin_data_object)
setupwp_plugin(self, 'nginx-helper',
'rt_wp_nginx_helper_options',
setupwp_plugin(self, 'litespeed-cache',
'litespeed-cache-conf',
plugin_data, data)
except SiteError as e:
Log.debug(self, str(e))
Log.info(self, Log.FAIL + "Update nginx-helper "
Log.info(self, Log.FAIL + "Update litespeed-cache "
"settings failed. "
"Check the log for details:"
" `tail /var/log/wo/wordops.log` "
@@ -869,35 +837,31 @@ class WOSiteUpdateController(CementBaseController):
data['wpredis'])):
try:
plugin_data_object = {
"log_level": "INFO",
"log_filesize": 5,
"enable_purge": 1,
"enable_map": "0",
"enable_log": 0,
"enable_stamp": 1,
"purge_homepage_on_new": 1,
"purge_homepage_on_edit": 1,
"purge_homepage_on_del": 1,
"purge_archive_on_new": 1,
"purge_archive_on_edit": 0,
"purge_archive_on_del": 0,
"purge_archive_on_new_comment": 0,
"purge_archive_on_deleted_comment": 0,
"purge_page_on_mod": 1,
"purge_page_on_new_comment": 1,
"purge_page_on_deleted_comment": 1,
"cache_method": "enable_redis",
"purge_method": "get_request",
"redis_hostname": "127.0.0.1",
"redis_port": "6379",
"redis_prefix": "nginx-cache:"}
"cache-lscwp": "on",
"cache-pub_priv_ttl": 604800,
"cache-commenter": "on",
"cache-rest": "on",
"cache-page_login": "on",
"cache-favicon": "on",
"cache-resources": "on",
"cache-mobile": "on",
"cache-nocache_cookies": "",
"cache-nocache_useragents": "",
"purge-purge_on_upgrade": "on",
"purge-auto_purge": "on",
"purge-stale": "on",
"purge-hook_all": "on",
"cache-object": "on",
"cache-object_kind": "redis",
"cache-object_host": "127.0.0.1",
"cache-object_port": "6379"}
plugin_data = json.dumps(plugin_data_object)
setupwp_plugin(self, 'nginx-helper',
'rt_wp_nginx_helper_options',
setupwp_plugin(self, 'litespeed-cache',
'litespeed-cache-conf',
plugin_data, data)
except SiteError as e:
Log.debug(self, str(e))
Log.info(self, Log.FAIL + "Update nginx-helper "
Log.info(self, Log.FAIL + "Update litespeed-cache "
"settings failed. "
"Check the log for details:"
" `tail /var/log/wo/wordops.log` "
@@ -905,37 +869,27 @@ class WOSiteUpdateController(CementBaseController):
return 1
else:
try:
# disable nginx-helper
# disable litespeed-cache
plugin_data_object = {
"log_level": "INFO",
"log_filesize": 5,
"enable_purge": 0,
"enable_map": 0,
"enable_log": 0,
"enable_stamp": 0,
"purge_homepage_on_new": 1,
"purge_homepage_on_edit": 1,
"purge_homepage_on_del": 1,
"purge_archive_on_new": 1,
"purge_archive_on_edit": 0,
"purge_archive_on_del": 0,
"purge_archive_on_new_comment": 0,
"purge_archive_on_deleted_comment": 0,
"purge_page_on_mod": 1,
"purge_page_on_new_comment": 1,
"purge_page_on_deleted_comment": 1,
"cache_method": "enable_redis",
"purge_method": "get_request",
"redis_hostname": "127.0.0.1",
"redis_port": "6379",
"redis_prefix": "nginx-cache:"}
"cache-lscwp": "off",
"cache-pub_priv_ttl": 0,
"cache-commenter": "off",
"cache-rest": "off",
"cache-page_login": "off",
"cache-favicon": "off",
"cache-resources": "off",
"cache-mobile": "off",
"purge-purge_on_upgrade": "off",
"purge-auto_purge": "off",
"purge-stale": "off",
"purge-hook_all": "off"}
plugin_data = json.dumps(plugin_data_object)
setupwp_plugin(
self, 'nginx-helper',
'rt_wp_nginx_helper_options', plugin_data, data)
self, 'litespeed-cache',
'litespeed-cache-conf', plugin_data, data)
except SiteError as e:
Log.debug(self, str(e))
Log.info(self, Log.FAIL + "Update nginx-helper "
Log.info(self, Log.FAIL + "Update litespeed-cache "
"settings failed. "
"Check the log for details:"
" `tail /var/log/wo/wordops.log` "
@@ -1026,12 +980,12 @@ class WOSiteUpdateController(CementBaseController):
"`tail /var/log/wo/wordops.log` and please try again")
return 1
# Service Nginx Reload
if not WOService.reload_service(self, 'nginx'):
Log.error(self, "service nginx reload failed. "
"check issues with `nginx -t` command")
# Service OLS Reload
if not WOService.reload_service(self, 'lsws'):
Log.error(self, "service lsws reload failed. "
"check issues with OpenLiteSpeed config")
WOGit.add(self, ["/etc/nginx"],
WOGit.add(self, [WOVar.wo_ols_conf_dir],
msg="{0} updated with {1} {2}"
.format(wo_www_domain, stype, cache))
# Setup Permissions for webroot