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

@@ -1,5 +1,6 @@
import getpass
import os
import shutil
from cement.core.controller import CementBaseController, expose
@@ -40,6 +41,12 @@ class WOSecureController(CementBaseController):
(['--allowpassword'], dict(
help='allow password authentification '
'when hardening ssh security', action='store_true')),
(['--lockdown'], dict(
help='enable WP Fort Knox lockdown on a site',
action='store_true')),
(['--unlock'], dict(
help='disable WP Fort Knox lockdown on a site',
action='store_true')),
(['--force'],
dict(help='force execution without being prompt',
action='store_true')),
@@ -62,12 +69,16 @@ class WOSecureController(CementBaseController):
self.secure_ssh_port()
if pargs.ssh:
self.secure_ssh()
if pargs.lockdown:
self.secure_lockdown()
if pargs.unlock:
self.secure_unlock()
@expose(hide=True)
def secure_auth(self):
"""This function secures authentication"""
WOGit.add(self, ["/etc/nginx"],
msg="Add Nginx to into Git")
WOGit.add(self, [WOVar.wo_ols_conf_dir],
msg="Add OLS config to Git")
pargs = self.app.pargs
passwd = RANDOM.long(self)
if not pargs.user_input:
@@ -82,25 +93,21 @@ class WOSecureController(CementBaseController):
pargs.user_pass = password
if password == "":
pargs.user_pass = passwd
Log.debug(self, "printf username:"
"$(openssl passwd --apr1 "
"password 2> /dev/null)\n\""
"> /etc/nginx/htpasswd-wo 2>/dev/null")
WOShellExec.cmd_exec(self, "printf \"{username}:"
"$(openssl passwd -apr1 "
"{password} 2> /dev/null)\n\""
"> /etc/nginx/htpasswd-wo 2>/dev/null"
.format(username=pargs.user_input,
password=pargs.user_pass),
log=False)
WOGit.add(self, ["/etc/nginx"],
# Set OLS admin password using admpass.sh
WOShellExec.cmd_exec(
self, "/usr/local/lsws/admin/misc/admpass.sh "
"{username} {password}"
.format(username=pargs.user_input,
password=pargs.user_pass),
log=False)
WOGit.add(self, [WOVar.wo_ols_conf_dir],
msg="Adding changed secure auth into Git")
@expose(hide=True)
def secure_port(self):
"""This function Secures port"""
WOGit.add(self, ["/etc/nginx"],
msg="Add Nginx to into Git")
WOGit.add(self, [WOVar.wo_ols_conf_dir],
msg="Add OLS config to Git")
pargs = self.app.pargs
if pargs.user_input:
while ((not pargs.user_input.isdigit()) and
@@ -117,25 +124,27 @@ class WOSecureController(CementBaseController):
Log.info(self, "Please Enter valid port number :")
port = input("WordOps admin port [22222]:")
pargs.user_input = port
data = dict(release=WOVar.wo_version,
port=pargs.user_input, webroot='/var/www/')
WOTemplate.deploy(
self, '/etc/nginx/sites-available/22222',
'22222.mustache', data)
WOGit.add(self, ["/etc/nginx"],
# Update OLS backend listener port
httpd_conf = '{0}/httpd_config.conf'.format(WOVar.wo_ols_conf_dir)
if os.path.isfile(httpd_conf):
WOFileUtils.searchreplace(
self, httpd_conf,
'address *:22222',
'address *:{0}'.format(pargs.user_input))
WOGit.add(self, [WOVar.wo_ols_conf_dir],
msg="Adding changed secure port into Git")
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 `{0} -t` command"
.format(WOVar.wo_ols_bin))
Log.info(self, "Successfully port changed {port}"
.format(port=pargs.user_input))
@expose(hide=True)
def secure_ip(self):
"""IP whitelisting"""
if os.path.exists('/etc/nginx'):
WOGit.add(self, ["/etc/nginx"],
msg="Add Nginx to into Git")
WOGit.add(self, [WOVar.wo_ols_conf_dir],
msg="Add OLS config to Git")
pargs = self.app.pargs
if not pargs.user_input:
ip = input("Enter the comma separated IP addresses "
@@ -146,17 +155,98 @@ class WOSecureController(CementBaseController):
except Exception as e:
Log.debug(self, "{0}".format(e))
user_ip = ['127.0.0.1']
for ip_addr in user_ip:
if not ("exist_ip_address " + ip_addr in open('/etc/nginx/common/'
'acl.conf').read()):
WOShellExec.cmd_exec(self, "sed -i "
"\"/deny/i allow {whitelist_address}\;\""
" /etc/nginx/common/acl.conf"
.format(whitelist_address=ip_addr))
WOGit.add(self, ["/etc/nginx"],
# Update OLS ACL configuration
acl_conf = '{0}/22222/vhconf.conf'.format(WOVar.wo_ols_vhost_dir)
if os.path.isfile(acl_conf):
for ip_addr in user_ip:
ip_addr = ip_addr.strip()
if not WOFileUtils.grepcheck(self, acl_conf, ip_addr):
WOFileUtils.searchreplace(
self, acl_conf,
'allowList',
'allowList\n {0}'.format(ip_addr))
WOGit.add(self, [WOVar.wo_ols_conf_dir],
msg="Adding changed secure ip into Git")
Log.info(self, "Successfully added IP address in access control")
Log.info(self, "Successfully added IP address in acl.conf file")
@expose(hide=True)
def secure_lockdown(self):
"""Enable WP Fort Knox lockdown on a WordPress site"""
pargs = self.app.pargs
if not pargs.user_input:
site_name = input("Enter the site name to lockdown: ")
pargs.user_input = site_name
site_name = pargs.user_input
webroot = '{0}{1}'.format(WOVar.wo_webroot, site_name)
mu_plugins_dir = '{0}/htdocs/wp-content/mu-plugins'.format(webroot)
fort_knox_src = '/var/lib/wo/wp-fort-knox.php'
if not os.path.isdir(webroot):
Log.error(self, "Site {0} not found".format(site_name))
# Check if it's a WordPress site
if not os.path.isfile(
'{0}/htdocs/wp-config.php'.format(webroot)):
Log.error(self, "Site {0} is not a WordPress site"
.format(site_name))
# Check if Fort Knox source exists
if not os.path.isfile(fort_knox_src):
Log.error(self, "WP Fort Knox plugin not found at {0}. "
"Please reinstall WordOps.".format(fort_knox_src))
# Create mu-plugins directory if it doesn't exist
if not os.path.isdir(mu_plugins_dir):
WOFileUtils.mkdir(self, mu_plugins_dir)
fort_knox_dest = '{0}/wp-fort-knox.php'.format(mu_plugins_dir)
if os.path.isfile(fort_knox_dest):
Log.info(self, "WP Fort Knox is already enabled for {0}"
.format(site_name))
return
Log.wait(self, "Enabling WP Fort Knox lockdown")
shutil.copy2(fort_knox_src, fort_knox_dest)
WOFileUtils.chown(
self, fort_knox_dest,
WOVar.wo_php_user, WOVar.wo_php_user)
Log.valide(self, "Enabling WP Fort Knox lockdown")
Log.info(self, "WP Fort Knox enabled for {0}\n"
" File modifications and plugin management "
"are now disabled in wp-admin.\n"
" Use WP-CLI for all administrative tasks.\n"
" To disable: wo secure --unlock {0}"
.format(site_name))
@expose(hide=True)
def secure_unlock(self):
"""Disable WP Fort Knox lockdown on a WordPress site"""
pargs = self.app.pargs
if not pargs.user_input:
site_name = input("Enter the site name to unlock: ")
pargs.user_input = site_name
site_name = pargs.user_input
webroot = '{0}{1}'.format(WOVar.wo_webroot, site_name)
fort_knox_path = ('{0}/htdocs/wp-content/mu-plugins/'
'wp-fort-knox.php'.format(webroot))
if not os.path.isdir(webroot):
Log.error(self, "Site {0} not found".format(site_name))
if not os.path.isfile(fort_knox_path):
Log.info(self, "WP Fort Knox is not enabled for {0}"
.format(site_name))
return
Log.wait(self, "Disabling WP Fort Knox lockdown")
WOFileUtils.rm(self, fort_knox_path)
Log.valide(self, "Disabling WP Fort Knox lockdown")
Log.info(self, "WP Fort Knox disabled for {0}\n"
" Plugin management is now available in wp-admin."
.format(site_name))
@expose(hide=True)
def secure_ssh(self):