Files
WPIQ/wo/cli/plugins/stack_upgrade.py
Malin fa5bf17eb8
Some checks failed
CI / test WordOps (ubuntu-22.04) (push) Has been cancelled
CI / test WordOps (ubuntu-24.04) (push) Has been cancelled
feat: convert WordOps from Nginx to OpenLiteSpeed + LSPHP + LSCache
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>
2026-02-08 18:55:16 +01:00

398 lines
17 KiB
Python

import os
import shutil
from cement.core.controller import CementBaseController, expose
from wo.cli.plugins.stack_pref import post_pref, pre_pref, pre_stack
from wo.core.aptget import WOAptGet
from wo.core.download import WODownload
from wo.core.extract import WOExtract
from wo.core.fileutils import WOFileUtils
from wo.core.logging import Log
from wo.core.shellexec import WOShellExec
from wo.core.variables import WOVar
from wo.core.services import WOService
from wo.core.mysql import WOMysql
class WOStackUpgradeController(CementBaseController):
class Meta:
label = 'upgrade'
stacked_on = 'stack'
stacked_type = 'nested'
description = ('Upgrade stack safely')
arguments = [
(['--all'],
dict(help='Upgrade all stack', action='store_true')),
(['--web'],
dict(help='Upgrade web stack', action='store_true')),
(['--admin'],
dict(help='Upgrade admin tools stack', action='store_true')),
(['--security'],
dict(help='Upgrade security stack', action='store_true')),
(['--nginx'],
dict(help='Upgrade OpenLiteSpeed stack', action='store_true')),
(['--php'],
dict(help='Upgrade PHP stack', action='store_true')),
(['--mysql'],
dict(help='Upgrade MySQL stack', action='store_true')),
(['--mariadb'],
dict(help='Upgrade MySQL stack alias',
action='store_true')),
(['--wpcli'],
dict(help='Upgrade WPCLI', action='store_true')),
(['--redis'],
dict(help='Upgrade Redis', action='store_true')),
(['--netdata'],
dict(help='Upgrade Netdata', action='store_true')),
(['--fail2ban'],
dict(help='Upgrade Fail2Ban', action='store_true')),
(['--dashboard'],
dict(help='Upgrade WordOps Dashboard', action='store_true')),
(['--composer'],
dict(help='Upgrade Composer', action='store_true')),
(['--mysqltuner'],
dict(help='Upgrade MySQLTuner', action='store_true')),
(['--phpmyadmin'],
dict(help='Upgrade phpMyAdmin', action='store_true')),
(['--adminer'],
dict(help='Upgrade Adminer', action='store_true')),
(['--no-prompt'],
dict(help="Upgrade Packages without any prompt",
action='store_true')),
(['--force'],
dict(help="Force Packages upgrade without any prompt",
action='store_true')),
]
for php_version, php_number in WOVar.wo_php_versions.items():
arguments.append(([f'--{php_version}'],
dict(help=f'Upgrade PHP {php_number} stack',
action='store_true')))
@expose(hide=True)
def default(self, disp_msg=False):
# All package update
apt_packages = []
packages = []
self.msg = []
pargs = self.app.pargs
wo_phpmyadmin = WODownload.pma_release(self)
if all(value is None or value is False for value in vars(pargs).values()):
pargs.web = True
pargs.admin = True
pargs.security = True
if pargs.mariadb:
pargs.mysql = True
if pargs.php:
if self.app.config.has_section('php'):
config_php_ver = self.app.config.get(
'php', 'version')
current_php = config_php_ver.replace(".", "")
setattr(self.app.pargs, 'php{0}'.format(current_php), True)
if pargs.all:
pargs.web = True
pargs.admin = True
pargs.security = True
pargs.redis = True
if pargs.web:
pargs.nginx = True
pargs.php74 = True
pargs.php80 = True
pargs.php81 = True
pargs.php82 = True
pargs.php83 = True
pargs.php84 = True
pargs.php85 = True
pargs.mysql = True
pargs.wpcli = True
if pargs.admin:
pargs.netdata = True
pargs.composer = True
pargs.dashboard = True
pargs.phpmyadmin = True
pargs.wpcli = True
pargs.adminer = True
pargs.mysqltuner = True
if pargs.security:
pargs.fail2ban = True
# OpenLiteSpeed
if pargs.nginx:
if WOAptGet.is_installed(self, 'openlitespeed'):
apt_packages = apt_packages + WOVar.wo_ols
else:
if os.path.isfile('/usr/local/lsws/bin/openlitespeed'):
Log.info(self, "Updating OpenLiteSpeed templates")
post_pref(self, WOVar.wo_ols, [])
else:
Log.info(self, "OpenLiteSpeed is not already installed")
wo_vars = {
'php74': WOVar.wo_php74,
'php80': WOVar.wo_php80,
'php81': WOVar.wo_php81,
'php82': WOVar.wo_php82,
'php83': WOVar.wo_php83,
'php84': WOVar.wo_php84,
'php85': WOVar.wo_php85,
}
for parg_version, version in WOVar.wo_php_versions.items():
if getattr(pargs, parg_version, False):
short_ver = version.replace('.', '')
Log.debug(self, f"Setting apt_packages variable for PHP {version}")
if WOAptGet.is_installed(self, f'lsphp{short_ver}'):
apt_packages = apt_packages + wo_vars[parg_version] + WOVar.wo_php_extra
else:
Log.debug(self, f"PHP {version} not installed")
Log.info(self, f"PHP {version} not installed")
# mysql
if pargs.mysql:
if WOMysql.mariadb_ping(self):
apt_packages = apt_packages + ['mariadb-server']
# redis
if pargs.redis:
if WOAptGet.is_installed(self, 'redis-server'):
apt_packages = apt_packages + ['redis-server']
# fail2ban
if pargs.fail2ban:
if WOAptGet.is_installed(self, 'fail2ban'):
apt_packages = apt_packages + ['fail2ban']
# wp-cli
if pargs.wpcli:
if os.path.isfile('/usr/local/bin/wp'):
packages = packages + [[f"{WOVar.wpcli_url}",
"/usr/local/bin/wp",
"WP-CLI"]]
else:
Log.info(self, "WPCLI is not installed with WordOps")
# netdata
if pargs.netdata:
# detect static binaries install
if (os.path.isdir('/opt/netdata') or
os.path.isdir('/etc/netdata')):
packages = packages + [[
f"{WOVar.netdata_script_url}",
'/var/lib/wo/tmp/kickstart.sh', 'Netdata']]
else:
Log.info(self, 'Netdata is not installed')
# wordops dashboard
if pargs.dashboard:
if (os.path.isfile('/var/www/22222/htdocs/index.php') or
os.path.isfile('/var/www/22222/htdocs/index.html')):
packages = packages + [[
"https://github.com/WordOps/wordops-dashboard/"
"releases/download/v{0}/wordops-dashboard.tar.gz"
.format(WOVar.wo_dashboard),
"/var/lib/wo/tmp/wo-dashboard.tar.gz",
"WordOps Dashboard"]]
else:
Log.info(self, 'WordOps dashboard is not installed')
# phpmyadmin
if pargs.phpmyadmin:
if os.path.isdir('/var/www/22222/htdocs/db/pma'):
packages = packages + [[
"https://files.phpmyadmin.net"
"/phpMyAdmin/{0}/phpMyAdmin-{0}-"
"all-languages.tar.gz"
.format(wo_phpmyadmin),
"/var/lib/wo/tmp/pma.tar.gz",
"PHPMyAdmin"]]
else:
Log.info(self, "phpMyAdmin isn't installed")
# adminer
if pargs.adminer:
if os.path.isfile("{0}22222/htdocs/db/"
"adminer/index.php"
.format(WOVar.wo_webroot)):
Log.debug(self, "Setting packages variable for Adminer ")
packages = packages + [[
"https://www.adminer.org/latest.php",
"{0}22222/"
"htdocs/db/adminer/index.php"
.format(WOVar.wo_webroot),
"Adminer"],
["https://raw.githubusercontent.com"
"/vrana/adminer/master/designs/"
"pepa-linha/adminer.css",
"{0}22222/"
"htdocs/db/adminer/adminer.css"
.format(WOVar.wo_webroot),
"Adminer theme"]]
else:
Log.debug(self, "Adminer isn't installed")
Log.info(self, "Adminer isn't installed")
# composer
if pargs.composer:
if os.path.isfile('/usr/local/bin/composer'):
packages = packages + [[
"https://getcomposer.org/installer",
"/var/lib/wo/tmp/composer-install",
"Composer"]]
else:
Log.info(self, "Composer isn't installed")
# mysqltuner
if pargs.mysqltuner:
if WOAptGet.is_exec(self, 'mysqltuner'):
Log.debug(self, "Setting packages variable "
"for MySQLTuner ")
packages = packages + [["https://raw."
"githubusercontent.com/"
"major/MySQLTuner-perl"
"/master/mysqltuner.pl",
"/usr/bin/mysqltuner",
"MySQLTuner"]]
if not apt_packages and not packages:
self.app.args.print_help()
else:
pre_stack(self)
if apt_packages:
# Check if critical packages are being upgraded
has_critical = False
for pkg in apt_packages:
if pkg in ['openlitespeed', 'redis-server',
'mariadb-server'] or pkg.startswith('lsphp'):
has_critical = True
break
if has_critical:
Log.warn(
self, "Your sites may be down for few seconds if "
"you are upgrading OpenLiteSpeed, LSPHP, "
"MariaDB or Redis")
# Check prompt
if not (pargs.no_prompt or pargs.force):
start_upgrade = input("Do you want to continue:[y/N]")
if start_upgrade != "Y" and start_upgrade != "y":
Log.error(self, "Not starting package update")
# additional pre_pref
if "openlitespeed" in apt_packages:
pre_pref(self, WOVar.wo_ols)
Log.wait(self, "Updating APT cache")
# apt-get update
WOAptGet.update(self)
Log.valide(self, "Updating APT cache")
# redis pre_pref
if "redis-server" in apt_packages:
pre_pref(self, WOVar.wo_redis)
# upgrade packages
WOAptGet.install(self, apt_packages)
Log.wait(self, "Configuring APT Packages")
post_pref(self, apt_packages, [], True)
Log.valide(self, "Configuring APT Packages")
# Post Actions after package updates
if packages:
if WOAptGet.is_selected(self, 'WP-CLI', packages):
WOFileUtils.rm(self, '/usr/local/bin/wp')
if WOAptGet.is_selected(self, 'Netdata', packages):
WOFileUtils.rm(self, '/var/lib/wo/tmp/kickstart.sh')
if WOAptGet.is_selected(self, 'WordOps Dashboard', packages):
if os.path.isfile('/var/www/22222/htdocs/index.php'):
WOFileUtils.rm(self, '/var/www/22222/htdocs/index.php')
if os.path.isfile('/var/www/22222/htdocs/index.html'):
WOFileUtils.rm(
self, '/var/www/22222/htdocs/index.html')
Log.debug(self, "Downloading following: {0}".format(packages))
WODownload.download(self, packages)
if WOAptGet.is_selected(self, 'WP-CLI', packages):
WOFileUtils.chmod(self, "/usr/local/bin/wp", 0o775)
if WOAptGet.is_selected(self, 'MySQLTuner', packages):
WOFileUtils.chmod(self, "/usr/bin/mysqltuner", 0o775)
if os.path.exists('/usr/local/bin/mysqltuner'):
WOFileUtils.rm(self, '/usr/local/bin/mysqltuner')
# Netdata
if WOAptGet.is_selected(self, 'Netdata', packages):
WOService.stop_service(self, 'netdata')
if os.path.exists('/opt/netdata/usr/libexec/netdata/netdata-uninstaller.sh'):
WOShellExec.cmd_exec(self,
"/opt/netdata/usr/libexec/"
"netdata/netdata-uninstaller.sh --yes --force",
log=False)
Log.wait(self, "Upgrading Netdata")
# detect static binaries install
WOShellExec.cmd_exec(
self,
"bash /var/lib/wo/tmp/kickstart.sh "
"--dont-wait --no-updates --stable-channel "
"--reinstall-even-if-unsafe",
errormsg='', log=False)
Log.valide(self, "Upgrading Netdata")
if WOAptGet.is_selected(self, 'WordOps Dashboard', packages):
post_pref(
self, [], [["https://github.com/WordOps"
"/wordops-dashboard/"
"releases/download/v{0}/"
"wordops-dashboard.tar.gz"
.format(WOVar.wo_dashboard),
"/var/lib/wo/tmp/wo-dashboard.tar.gz",
"WordOps Dashboard"]])
if WOAptGet.is_selected(self, 'Composer', packages):
Log.wait(self, "Upgrading Composer")
if WOShellExec.cmd_exec(
self, '/usr/bin/php -v'):
WOShellExec.cmd_exec(
self, "php -q /var/lib/wo"
"/tmp/composer-install "
"--install-dir=/var/lib/wo/tmp/")
shutil.copyfile('/var/lib/wo/tmp/composer.phar',
'/usr/local/bin/composer')
WOFileUtils.chmod(self, "/usr/local/bin/composer", 0o775)
Log.valide(self, "Upgrading Composer ")
if WOAptGet.is_selected(self, 'PHPMyAdmin', packages):
Log.wait(self, "Upgrading phpMyAdmin")
WOExtract.extract(self, '/var/lib/wo/tmp/pma.tar.gz',
'/var/lib/wo/tmp/')
shutil.copyfile(('{0}22222/htdocs/db/pma'
'/config.inc.php'
.format(WOVar.wo_webroot)),
('/var/lib/wo/tmp/phpMyAdmin-{0}'
'-all-languages/config.inc.php'
.format(wo_phpmyadmin))
)
WOFileUtils.rm(self, '{0}22222/htdocs/db/pma'
.format(WOVar.wo_webroot))
shutil.move('/var/lib/wo/tmp/phpMyAdmin-{0}'
'-all-languages/'
.format(wo_phpmyadmin),
'{0}22222/htdocs/db/pma/'
.format(WOVar.wo_webroot))
WOFileUtils.chown(self, "{0}22222/htdocs"
.format(WOVar.wo_webroot),
'www-data',
'www-data', recursive=True)
Log.valide(self, "Upgrading phpMyAdmin")
if os.path.exists('{0}22222/htdocs'.format(WOVar.wo_webroot)):
WOFileUtils.chown(self, "{0}22222/htdocs"
.format(WOVar.wo_webroot),
'www-data',
'www-data', recursive=True)
Log.info(self, "Successfully updated packages")