Files
CyberPanel/install/installCyberPanel.py
Master3395 41ead838ef Add sieve by default.
Add sieve by default.
2025-09-10 20:01:52 +02:00

1050 lines
46 KiB
Python

import shutil
import subprocess
import os
from mysqlUtilities import mysqlUtilities
import installLog as logging
import errno
import MySQLdb as mariadb
import install
from os.path import exists
import time
import install_utils
import urllib.request
import re
# distros - using from install_utils
centos = install_utils.centos
ubuntu = install_utils.ubuntu
cent8 = install_utils.cent8
openeuler = install_utils.openeuler
def get_Ubuntu_release():
return install_utils.get_Ubuntu_release(use_print=True, exit_on_error=True)
def get_Ubuntu_code_name():
"""Get Ubuntu codename based on version"""
release = get_Ubuntu_release()
if release >= 24.04:
return "noble"
elif release >= 22.04:
return "jammy"
elif release >= 20.04:
return "focal"
elif release >= 18.04:
return "bionic"
else:
return "xenial"
# Using shared function from install_utils
FetchCloudLinuxAlmaVersionVersion = install_utils.FetchCloudLinuxAlmaVersionVersion
class InstallCyberPanel:
mysql_Root_password = ""
mysqlPassword = ""
CloudLinux8 = 0
def install_package(self, package_name, options=""):
"""Unified package installation across distributions"""
command, shell = install_utils.get_package_install_command(self.distro, package_name, options)
# InstallCyberPanel always uses verbose mode (no silent option)
if self.distro == ubuntu:
return install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, shell)
else:
# For non-Ubuntu, original code didn't pass shell parameter
return install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
def manage_service(self, service_name, action="start"):
"""Unified service management"""
service_map = {
'mariadb': 'mariadb',
'pureftpd': 'pure-ftpd-mysql' if self.distro == ubuntu else 'pure-ftpd',
'pdns': 'pdns'
}
actual_service = service_map.get(service_name, service_name)
# For AlmaLinux 9, try both mariadb and mysqld services
if service_name == 'mariadb' and (self.distro == cent8 or self.distro == openeuler):
# Try mariadb first, then mysqld if mariadb fails
command = f'systemctl {action} {actual_service}'
result = install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
if result != 0:
# If mariadb service fails, try mysqld
command = f'systemctl {action} mysqld'
return install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
return result
else:
command = f'systemctl {action} {actual_service}'
return install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
def modify_file_content(self, file_path, replacements):
"""Generic file content modification"""
try:
with open(file_path, 'r') as f:
data = f.readlines()
with open(file_path, 'w') as f:
for line in data:
modified_line = line
for old, new in replacements.items():
if old in line:
modified_line = line.replace(old, new)
break
f.write(modified_line)
return True
except IOError as e:
logging.InstallLog.writeToFile(f'[ERROR] {str(e)} [modify_file_content]')
return False
def copy_config_file(self, source_dir, dest_path, mysql_mode='One'):
"""Handle configuration file copying with mode selection"""
# For directories like 'dns' vs 'dns-one', 'pure-ftpd' vs 'pure-ftpd-one'
# Default mode is 'One' which uses the -one directories
if mysql_mode == 'Two':
source_path = source_dir
else:
# Default mode 'One' uses directories with -one suffix
source_path = f"{source_dir}-one"
# Ensure we're working with absolute paths
if not os.path.isabs(source_path):
source_path = os.path.join(self.cwd, source_path)
# Determine the actual file to copy
if os.path.isdir(source_path):
# If dest_path is a file (like pdns.conf), copy the specific file
if dest_path.endswith('.conf'):
# Look for the specific config file
source_file = os.path.join(source_path, os.path.basename(dest_path))
if os.path.exists(source_file):
if os.path.exists(dest_path):
os.remove(dest_path)
shutil.copy(source_file, dest_path)
else:
raise IOError(f"Source file {source_file} not found")
else:
# If it's a directory, copy the whole directory
if os.path.exists(dest_path):
if os.path.isdir(dest_path):
shutil.rmtree(dest_path)
shutil.copytree(source_path, dest_path)
else:
raise IOError(f"Source path {source_path} not found")
@staticmethod
def ISARM():
try:
command = 'uname -a'
try:
result = subprocess.run(command, capture_output=True, universal_newlines=True, shell=True)
except:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True)
if 'aarch64' in result.stdout:
return True
else:
return False
except:
return False
@staticmethod
def OSFlags():
if os.path.exists("/etc/redhat-release"):
data = open('/etc/redhat-release', 'r').read()
if data.find('CloudLinux 8') > -1 or data.find('cloudlinux 8') > -1:
InstallCyberPanel.CloudLinux8 = 1
def __init__(self, rootPath, cwd, distro, ent, serial=None, port=None, ftp=None, dns=None, publicip=None,
remotemysql=None, mysqlhost=None, mysqldb=None, mysqluser=None, mysqlpassword=None, mysqlport=None):
self.server_root_path = rootPath
self.cwd = cwd
self.distro = distro
self.ent = ent
self.serial = serial
self.port = port
self.ftp = None
self.dns = dns
self.publicip = publicip
self.remotemysql = remotemysql
self.mysqlhost = mysqlhost
self.mysqluser = mysqluser
self.mysqlpassword = mysqlpassword
self.mysqlport = mysqlport
self.mysqldb = mysqldb
## TURN ON OS FLAGS FOR SPECIFIC NEEDS LATER
InstallCyberPanel.OSFlags()
@staticmethod
def stdOut(message, log=0, exit=0, code=os.EX_OK):
install_utils.stdOut(message, log, exit, code)
@staticmethod
def getLatestLSWSVersion():
"""Fetch the latest LSWS Enterprise version from LiteSpeed's website"""
try:
# Try to fetch from the download page
url = "https://www.litespeedtech.com/products/litespeed-web-server/download"
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req, timeout=10) as response:
html = response.read().decode('utf-8')
# Look for the latest version pattern: lsws-X.Y.Z-ent
version_pattern = r'lsws-(\d+\.\d+\.\d+)-ent'
versions = re.findall(version_pattern, html)
if versions:
# Get the latest version
latest_version = sorted(versions, key=lambda v: [int(x) for x in v.split('.')])[-1]
InstallCyberPanel.stdOut(f"Found latest LSWS Enterprise version: {latest_version}", 1)
return latest_version
else:
InstallCyberPanel.stdOut("Could not find version pattern in HTML, using fallback", 1)
except Exception as e:
InstallCyberPanel.stdOut(f"Failed to fetch latest LSWS version: {str(e)}, using fallback", 1)
# Fallback to known latest version
return "6.3.4"
def installLiteSpeed(self):
if self.ent == 0:
self.install_package('openlitespeed')
else:
try:
try:
command = 'groupadd nobody'
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
except:
pass
try:
command = 'usermod -a -G nobody nobody'
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
except:
pass
# Get the latest LSWS Enterprise version dynamically
lsws_version = InstallCyberPanel.getLatestLSWSVersion()
if InstallCyberPanel.ISARM():
command = f'wget https://www.litespeedtech.com/packages/6.0/lsws-{lsws_version}-ent-aarch64-linux.tar.gz'
else:
command = f'wget https://www.litespeedtech.com/packages/6.0/lsws-{lsws_version}-ent-x86_64-linux.tar.gz'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
if InstallCyberPanel.ISARM():
command = f'tar zxf lsws-{lsws_version}-ent-aarch64-linux.tar.gz'
else:
command = f'tar zxf lsws-{lsws_version}-ent-x86_64-linux.tar.gz'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
if str.lower(self.serial) == 'trial':
command = f'wget -q --output-document=lsws-{lsws_version}/trial.key http://license.litespeedtech.com/reseller/trial.key'
if self.serial == '1111-2222-3333-4444':
command = f'wget -q --output-document=/root/cyberpanel/install/lsws-{lsws_version}/trial.key http://license.litespeedtech.com/reseller/trial.key'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
else:
writeSerial = open(f'lsws-{lsws_version}/serial.no', 'w')
writeSerial.writelines(self.serial)
writeSerial.close()
shutil.copy('litespeed/install.sh', f'lsws-{lsws_version}/')
shutil.copy('litespeed/functions.sh', f'lsws-{lsws_version}/')
os.chdir(f'lsws-{lsws_version}')
command = 'chmod +x install.sh'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
command = 'chmod +x functions.sh'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
command = './install.sh'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
os.chdir(self.cwd)
confPath = '/usr/local/lsws/conf/'
shutil.copy('litespeed/httpd_config.xml', confPath)
shutil.copy('litespeed/modsec.conf', confPath)
shutil.copy('litespeed/httpd.conf', confPath)
command = 'chown -R lsadm:lsadm ' + confPath
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
except BaseException as msg:
logging.InstallLog.writeToFile('[ERROR] ' + str(msg) + " [installLiteSpeed]")
return 0
return 1
def reStartLiteSpeed(self):
command = install_utils.format_restart_litespeed_command(self.server_root_path)
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
def fix_ols_configs(self):
try:
InstallCyberPanel.stdOut("Fixing OpenLiteSpeed configurations!", 1)
## remove example virtual host
data = open(self.server_root_path + "conf/httpd_config.conf", 'r').readlines()
writeDataToFile = open(self.server_root_path + "conf/httpd_config.conf", 'w')
for items in data:
if items.find("map") > -1 and items.find("Example") > -1:
continue
else:
writeDataToFile.writelines(items)
writeDataToFile.close()
InstallCyberPanel.stdOut("OpenLiteSpeed Configurations fixed!", 1)
except IOError as msg:
logging.InstallLog.writeToFile('[ERROR] ' + str(msg) + " [fix_ols_configs]")
return 0
return self.reStartLiteSpeed()
def changePortTo80(self):
try:
InstallCyberPanel.stdOut("Changing default port to 80..", 1)
file_path = self.server_root_path + "conf/httpd_config.conf"
if self.modify_file_content(file_path, {"*:8088": "*:80"}):
InstallCyberPanel.stdOut("Default port is now 80 for OpenLiteSpeed!", 1)
else:
return 0
except Exception as msg:
logging.InstallLog.writeToFile('[ERROR] ' + str(msg) + " [changePortTo80]")
return 0
return self.reStartLiteSpeed()
def installAllPHPVersions(self):
php_versions = ['71', '72', '73', '74', '80', '81', '82', '83']
if self.distro == ubuntu:
# Install base PHP 7.x packages
command = 'DEBIAN_FRONTEND=noninteractive apt-get -y install ' \
'lsphp7? lsphp7?-common lsphp7?-curl lsphp7?-dev lsphp7?-imap lsphp7?-intl lsphp7?-json ' \
'lsphp7?-ldap lsphp7?-mysql lsphp7?-opcache lsphp7?-pspell lsphp7?-recode ' \
'lsphp7?-sqlite3 lsphp7?-tidy'
os.system(command)
# Install PHP 8.x versions
for version in php_versions[4:]: # 80, 81, 82, 83
self.install_package(f'lsphp{version}*')
elif self.distro == centos:
# First install the group
command = 'yum -y groupinstall lsphp-all'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
InstallCyberPanel.stdOut("LiteSpeed PHPs successfully installed!", 1)
# Install individual PHP versions
for version in php_versions:
self.install_package(f'lsphp{version}*', '--skip-broken')
elif self.distro == cent8:
# Install PHP versions in batches with exclusions
exclude_flags = "--exclude lsphp73-pecl-zip --exclude *imagick*"
# First batch: PHP 7.x and 8.0
versions_batch1 = ' '.join([f'lsphp{v}*' for v in php_versions[:5]])
self.install_package(versions_batch1, f'{exclude_flags} --skip-broken')
# Second batch: PHP 8.1+
versions_batch2 = ' '.join([f'lsphp{v}*' for v in php_versions[5:]])
self.install_package(versions_batch2, f'{exclude_flags} --skip-broken')
elif self.distro == openeuler:
# Install all PHP versions at once
all_versions = ' '.join([f'lsphp{v}*' for v in php_versions])
self.install_package(all_versions)
if self.distro != ubuntu:
InstallCyberPanel.stdOut("LiteSpeed PHPs successfully installed!", 1)
def installSieve(self):
"""Install Sieve (Dovecot Sieve) for email filtering on all OS variants"""
try:
InstallCyberPanel.stdOut("Installing Sieve (Dovecot Sieve) for email filtering...", 1)
if self.distro == ubuntu:
# Install dovecot-sieve and dovecot-managesieved
self.install_package('dovecot-sieve dovecot-managesieved')
else:
# For CentOS/AlmaLinux/OpenEuler
self.install_package('dovecot-pigeonhole')
# Add Sieve port 4190 to firewall
from plogical.firewallUtilities import FirewallUtilities
FirewallUtilities.addSieveFirewallRule()
InstallCyberPanel.stdOut("Sieve successfully installed and configured!", 1)
return 1
except BaseException as msg:
logging.InstallLog.writeToFile('[ERROR] ' + str(msg) + " [installSieve]")
return 0
def installMySQL(self, mysql):
############## Install mariadb ######################
if self.distro == ubuntu:
command = 'DEBIAN_FRONTEND=noninteractive apt-get install software-properties-common apt-transport-https curl -y'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
command = "mkdir -p /etc/apt/keyrings"
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
command = "curl -o /etc/apt/keyrings/mariadb-keyring.pgp 'https://mariadb.org/mariadb_release_signing_key.pgp'"
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
RepoPath = '/etc/apt/sources.list.d/mariadb.sources'
RepoContent = f"""
# MariaDB 10.11 repository list - created 2023-12-11 07:53 UTC
# https://mariadb.org/download/
X-Repolib-Name: MariaDB
Types: deb
# deb.mariadb.org is a dynamic mirror if your preferred mirror goes offline. See https://mariadb.org/mirrorbits/ for details.
# URIs: https://deb.mariadb.org/10.11/ubuntu
URIs: https://mirrors.gigenet.com/mariadb/repo/10.11/ubuntu
Suites: jammy
Components: main main/debug
Signed-By: /etc/apt/keyrings/mariadb-keyring.pgp
"""
if get_Ubuntu_release() > 21.00:
command = 'curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version=10.11'
result = install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR, True)
# If the download fails, use manual repo configuration as fallback
if result != 1:
install_utils.writeToFile("MariaDB repo setup script failed, using manual configuration...")
# First, ensure directories exist
command = 'mkdir -p /usr/share/keyrings /etc/apt/sources.list.d'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
# Download and add MariaDB signing key
command = 'curl -fsSL https://mariadb.org/mariadb_release_signing_key.pgp | gpg --dearmor -o /usr/share/keyrings/mariadb-keyring.pgp'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
# Use multiple mirror options for better reliability
RepoPath = '/etc/apt/sources.list.d/mariadb.list'
codename = get_Ubuntu_code_name()
RepoContent = f"""# MariaDB 10.11 repository list - manual fallback
# Primary mirror
deb [arch=amd64,arm64,ppc64el,s390x signed-by=/usr/share/keyrings/mariadb-keyring.pgp] https://mirror.mariadb.org/repo/10.11/ubuntu {codename} main
# Alternative mirrors (uncomment if primary fails)
# deb [arch=amd64,arm64,ppc64el,s390x signed-by=/usr/share/keyrings/mariadb-keyring.pgp] https://mirrors.gigenet.com/mariadb/repo/10.11/ubuntu {codename} main
# deb [arch=amd64,arm64,ppc64el,s390x signed-by=/usr/share/keyrings/mariadb-keyring.pgp] https://ftp.osuosl.org/pub/mariadb/repo/10.11/ubuntu {codename} main
"""
WriteToFile = open(RepoPath, 'w')
WriteToFile.write(RepoContent)
WriteToFile.close()
install_utils.writeToFile("Manual MariaDB repository configuration completed.")
command = 'DEBIAN_FRONTEND=noninteractive apt-get update -y'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
command = "DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server -y"
elif self.distro == centos:
RepoPath = '/etc/yum.repos.d/mariadb.repo'
RepoContent = f"""
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.11/rhel8-amd64
module_hotfixes=1
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
"""
WriteToFile = open(RepoPath, 'w')
WriteToFile.write(RepoContent)
WriteToFile.close()
command = 'dnf install mariadb-server -y'
elif self.distro == cent8 or self.distro == openeuler:
clAPVersion = FetchCloudLinuxAlmaVersionVersion()
type = clAPVersion.split('-')[0]
version = int(clAPVersion.split('-')[1])
if type == 'cl' and version >= 88:
command = 'yum remove db-governor db-governor-mysql -y'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
command = 'yum install governor-mysql -y'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
command = '/usr/share/lve/dbgovernor/mysqlgovernor.py --mysql-version=mariadb106'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
command = '/usr/share/lve/dbgovernor/mysqlgovernor.py --install --yes'
else:
command = 'curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version=10.11'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
command = 'yum remove mariadb* -y'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
command = 'sudo dnf -qy module disable mariadb'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
command = 'sudo dnf module reset mariadb -y'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
# Disable problematic mariadb-maxscale repository to avoid 404 errors
command = 'dnf config-manager --disable mariadb-maxscale'
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR, True)
# Clear dnf cache to avoid repository issues
command = 'dnf clean all'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
command = 'dnf install MariaDB-server MariaDB-client MariaDB-backup -y'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
############## Start mariadb ######################
self.startMariaDB()
def changeMYSQLRootPassword(self):
if self.remotemysql == 'OFF':
if self.distro == ubuntu:
passwordCMD = "use mysql;DROP DATABASE IF EXISTS test;DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%%';GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY '%s';UPDATE user SET plugin='' WHERE User='root';flush privileges;" % (
InstallCyberPanel.mysql_Root_password)
else:
passwordCMD = "use mysql;DROP DATABASE IF EXISTS test;DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%%';GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY '%s';flush privileges;" % (
InstallCyberPanel.mysql_Root_password)
# For AlmaLinux 9, try mysql command first, then mariadb
if self.distro == cent8 or self.distro == openeuler:
command = 'mysql -u root -e "' + passwordCMD + '"'
result = install_utils.call(command, self.distro, command, command, 0, 0, os.EX_OSERR)
if result != 0:
# If mysql command fails, try mariadb
command = 'mariadb -u root -e "' + passwordCMD + '"'
install_utils.call(command, self.distro, command, command, 0, 0, os.EX_OSERR)
else:
command = 'mariadb -u root -e "' + passwordCMD + '"'
install_utils.call(command, self.distro, command, command, 0, 0, os.EX_OSERR)
def startMariaDB(self):
if self.remotemysql == 'OFF':
############## Start mariadb ######################
self.manage_service('mariadb', 'start')
############## Enable mariadb at system startup ######################
if os.path.exists('/etc/systemd/system/mysqld.service'):
os.remove('/etc/systemd/system/mysqld.service')
if os.path.exists('/etc/systemd/system/mariadb.service'):
os.remove('/etc/systemd/system/mariadb.service')
self.manage_service('mariadb', 'enable')
def fixMariaDB(self):
self.stdOut("Setup MariaDB so it can support Cyberpanel's needs")
conn = mariadb.connect(user='root', passwd=self.mysql_Root_password)
cursor = conn.cursor()
cursor.execute('set global innodb_file_per_table = on;')
try:
cursor.execute('set global innodb_file_format = Barracuda;')
cursor.execute('set global innodb_large_prefix = on;')
except BaseException as msg:
self.stdOut('%s. [ERROR:335]' % (str(msg)))
cursor.close()
conn.close()
try:
fileName = '/etc/mysql/mariadb.conf.d/50-server.cnf'
data = open(fileName, 'r').readlines()
writeDataToFile = open(fileName, 'w')
for line in data:
writeDataToFile.write(line.replace('utf8mb4', 'utf8'))
writeDataToFile.close()
except IOError as err:
self.stdOut("[ERROR] Error in setting: " + fileName + ": " + str(err), 1, 1, os.EX_OSERR)
# Use the manage_service method for consistent service management
if self.distro == cent8 or self.distro == openeuler:
# Try mariadb first, then mysqld
result = os.system('systemctl restart mariadb')
if result != 0:
os.system('systemctl restart mysqld')
else:
os.system('systemctl restart mariadb')
self.stdOut("MariaDB is now setup so it can support Cyberpanel's needs")
def installPureFTPD(self):
if self.distro == ubuntu:
self.install_package('pure-ftpd-mysql')
if get_Ubuntu_release() == 18.10:
# Special handling for Ubuntu 18.10
packages = [
('pure-ftpd-common_1.0.47-3_all.deb', 'wget https://rep.cyberpanel.net/pure-ftpd-common_1.0.47-3_all.deb'),
('pure-ftpd-mysql_1.0.47-3_amd64.deb', 'wget https://rep.cyberpanel.net/pure-ftpd-mysql_1.0.47-3_amd64.deb')
]
for filename, wget_cmd in packages:
install_utils.call(wget_cmd, self.distro, wget_cmd, wget_cmd, 1, 1, os.EX_OSERR)
command = f'dpkg --install --force-confold {filename}'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
else:
self.install_package('pure-ftpd')
####### Install pureftpd to system startup
command = "systemctl enable " + install.preFlightsChecks.pureFTPDServiceName(self.distro)
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
###### FTP Groups and user settings settings
command = 'groupadd -g 2001 ftpgroup'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
command = 'useradd -u 2001 -s /bin/false -d /bin/null -c "pureftpd user" -g ftpgroup ftpuser'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
def startPureFTPD(self):
############## Start pureftpd ######################
serviceName = install.preFlightsChecks.pureFTPDServiceName(self.distro)
# During fresh installation, don't start Pure-FTPd yet
# It will be started after Django migrations create the required tables
InstallCyberPanel.stdOut("Pure-FTPd enabled for startup.", 1)
InstallCyberPanel.stdOut("Note: Pure-FTPd will start after database setup is complete.", 1)
logging.InstallLog.writeToFile("Pure-FTPd enabled but not started - waiting for Django migrations")
def installPureFTPDConfigurations(self, mysql):
try:
## setup ssl for ftp
InstallCyberPanel.stdOut("Configuring PureFTPD..", 1)
try:
if not os.path.exists("/etc/ssl/private"):
os.makedirs("/etc/ssl/private", mode=0o755)
except OSError as e:
if e.errno != errno.EEXIST:
logging.InstallLog.writeToFile("[ERROR] Could not create directory for FTP SSL: " + str(e))
raise
if (self.distro == centos or self.distro == cent8 or self.distro == openeuler) or (
self.distro == ubuntu and get_Ubuntu_release() == 18.14):
command = 'openssl req -newkey rsa:1024 -new -nodes -x509 -days 3650 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" -keyout /etc/ssl/private/pure-ftpd.pem -out /etc/ssl/private/pure-ftpd.pem'
else:
command = 'openssl req -x509 -nodes -days 7300 -newkey rsa:2048 -subj "/C=US/ST=Denial/L=Sprinal-ield/O=Dis/CN=www.example.com" -keyout /etc/ssl/private/pure-ftpd.pem -out /etc/ssl/private/pure-ftpd.pem'
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
os.chdir(self.cwd)
ftpdPath = "/etc/pure-ftpd"
self.copy_config_file("pure-ftpd", ftpdPath, mysql)
if self.distro == ubuntu:
try:
os.mkdir('/etc/pure-ftpd/conf')
os.mkdir('/etc/pure-ftpd/auth')
os.mkdir('/etc/pure-ftpd/db')
except OSError as err:
self.stdOut("[ERROR] Error creating extra pure-ftpd directories: " + str(err), ". Should be ok", 1)
data = open(ftpdPath + "/pureftpd-mysql.conf", "r").readlines()
writeDataToFile = open(ftpdPath + "/pureftpd-mysql.conf", "w")
dataWritten = "MYSQLPassword " + InstallCyberPanel.mysqlPassword + '\n'
for items in data:
if items.find("MYSQLPassword") > -1:
writeDataToFile.writelines(dataWritten)
else:
writeDataToFile.writelines(items)
writeDataToFile.close()
ftpConfPath = '/etc/pure-ftpd/pureftpd-mysql.conf'
if self.remotemysql == 'ON':
command = "sed -i 's|localhost|%s|g' %s" % (self.mysqlhost, ftpConfPath)
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
command = "sed -i 's|3306|%s|g' %s" % (self.mysqlport, ftpConfPath)
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
command = "sed -i 's|MYSQLSocket /var/lib/mysql/mysql.sock||g' %s" % (ftpConfPath)
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
if self.distro == ubuntu:
if os.path.exists('/etc/pure-ftpd/db/mysql.conf'):
os.remove('/etc/pure-ftpd/db/mysql.conf')
shutil.copy(ftpdPath + "/pureftpd-mysql.conf", '/etc/pure-ftpd/db/mysql.conf')
else:
shutil.copy(ftpdPath + "/pureftpd-mysql.conf", '/etc/pure-ftpd/db/mysql.conf')
command = 'echo 1 > /etc/pure-ftpd/conf/TLS'
subprocess.call(command, shell=True)
command = 'echo %s > /etc/pure-ftpd/conf/ForcePassiveIP' % (self.publicip)
subprocess.call(command, shell=True)
command = 'echo "40110 40210" > /etc/pure-ftpd/conf/PassivePortRange'
subprocess.call(command, shell=True)
command = 'echo "no" > /etc/pure-ftpd/conf/UnixAuthentication'
subprocess.call(command, shell=True)
command = 'echo "/etc/pure-ftpd/db/mysql.conf" > /etc/pure-ftpd/conf/MySQLConfigFile'
subprocess.call(command, shell=True)
command = 'ln -s /etc/pure-ftpd/conf/MySQLConfigFile /etc/pure-ftpd/auth/30mysql'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
command = 'ln -s /etc/pure-ftpd/conf/UnixAuthentication /etc/pure-ftpd/auth/65unix'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
command = 'systemctl restart pure-ftpd-mysql.service'
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
if get_Ubuntu_release() > 21.00:
### change mysql md5 to crypt
command = "sed -i 's/MYSQLCrypt md5/MYSQLCrypt crypt/g' /etc/pure-ftpd/db/mysql.conf"
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
command = "systemctl restart pure-ftpd-mysql.service"
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
else:
try:
clAPVersion = FetchCloudLinuxAlmaVersionVersion()
type = clAPVersion.split('-')[0]
version = int(clAPVersion.split('-')[1])
if type == 'al' and version >= 90:
command = "sed -i 's/MYSQLCrypt md5/MYSQLCrypt crypt/g' /etc/pure-ftpd/pureftpd-mysql.conf"
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
except:
pass
InstallCyberPanel.stdOut("PureFTPD configured!", 1)
except IOError as msg:
logging.InstallLog.writeToFile('[ERROR] ' + str(msg) + " [installPureFTPDConfigurations]")
return 0
def installPowerDNS(self):
try:
if self.distro == ubuntu or self.distro == cent8 or self.distro == openeuler:
# Stop and disable systemd-resolved
self.manage_service('systemd-resolved', 'stop')
self.manage_service('systemd-resolved.service', 'disable')
try:
os.rename('/etc/resolv.conf', '/etc/resolv.conf.bak')
except OSError as e:
if e.errno != errno.EEXIST and e.errno != errno.ENOENT:
InstallCyberPanel.stdOut("[ERROR] Unable to rename /etc/resolv.conf to install PowerDNS: " +
str(e), 1, 1, os.EX_OSERR)
# Create a temporary resolv.conf with Google DNS for package installation
try:
with open('/etc/resolv.conf', 'w') as f:
f.write('nameserver 8.8.8.8\n')
f.write('nameserver 8.8.4.4\n')
InstallCyberPanel.stdOut("Created temporary /etc/resolv.conf with Google DNS", 1)
except IOError as e:
InstallCyberPanel.stdOut("[ERROR] Unable to create /etc/resolv.conf: " + str(e), 1, 1, os.EX_OSERR)
# Install PowerDNS packages
if self.distro == ubuntu:
# Update package list first
command = "DEBIAN_FRONTEND=noninteractive apt-get update"
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
# Install PowerDNS packages
command = "DEBIAN_FRONTEND=noninteractive apt-get -y install pdns-server pdns-backend-mysql"
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR, True)
# Ensure service is stopped after installation for configuration
command = 'systemctl stop pdns || true'
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR, True)
return 1
else:
self.install_package('pdns pdns-backend-mysql')
except BaseException as msg:
logging.InstallLog.writeToFile('[ERROR] ' + str(msg) + " [powerDNS]")
def installPowerDNSConfigurations(self, mysqlPassword, mysql):
try:
InstallCyberPanel.stdOut("Configuring PowerDNS..", 1)
os.chdir(self.cwd)
if self.distro == centos or self.distro == cent8 or self.distro == openeuler:
dnsPath = "/etc/pdns/pdns.conf"
else:
dnsPath = "/etc/powerdns/pdns.conf"
# Ensure directory exists for Ubuntu
dnsDir = os.path.dirname(dnsPath)
if not os.path.exists(dnsDir):
try:
os.makedirs(dnsDir, mode=0o755)
except OSError as e:
if e.errno != errno.EEXIST:
raise
try:
self.copy_config_file("dns", dnsPath, mysql)
except Exception as e:
InstallCyberPanel.stdOut("[ERROR] Failed to copy PowerDNS config: " + str(e), 1)
logging.InstallLog.writeToFile('[ERROR] Failed to copy PowerDNS config: ' + str(e))
raise
# Verify the file was copied and has content
if not os.path.exists(dnsPath):
raise IOError(f"PowerDNS config file not found at {dnsPath} after copy")
# Check if file has content
with open(dnsPath, "r") as f:
content = f.read()
if not content or "launch=gmysql" not in content:
InstallCyberPanel.stdOut("[WARNING] PowerDNS config appears empty or incomplete, attempting to fix...", 1)
# First try to re-copy
try:
if os.path.exists(dnsPath):
os.remove(dnsPath)
source_file = os.path.join(self.cwd, "dns-one", "pdns.conf")
shutil.copy2(source_file, dnsPath)
except Exception as copy_error:
InstallCyberPanel.stdOut("[WARNING] Failed to re-copy config: " + str(copy_error), 1)
# Fallback: directly write the essential MySQL configuration
InstallCyberPanel.stdOut("[INFO] Directly writing MySQL backend configuration...", 1)
try:
mysql_config = f"""# PowerDNS MySQL Backend Configuration
launch=gmysql
gmysql-host=localhost
gmysql-port=3306
gmysql-user=cyberpanel
gmysql-password={mysqlPassword}
gmysql-dbname=cyberpanel
# Basic PowerDNS settings
daemon=no
guardian=no
setgid=pdns
setuid=pdns
"""
# If file exists and has some content, append our config
if os.path.exists(dnsPath) and content.strip():
# Check if it's just missing the MySQL part
with open(dnsPath, "a") as f:
f.write("\n\n" + mysql_config)
else:
# Write a complete minimal config
with open(dnsPath, "w") as f:
f.write(mysql_config)
InstallCyberPanel.stdOut("[SUCCESS] MySQL backend configuration written directly", 1)
except Exception as write_error:
InstallCyberPanel.stdOut("[ERROR] Failed to write MySQL config: " + str(write_error), 1)
raise
InstallCyberPanel.stdOut("PowerDNS config file prepared at: " + dnsPath, 1)
data = open(dnsPath, "r").readlines()
writeDataToFile = open(dnsPath, "w")
dataWritten = "gmysql-password=" + mysqlPassword + "\n"
for items in data:
if items.find("gmysql-password") > -1:
writeDataToFile.writelines(dataWritten)
else:
writeDataToFile.writelines(items)
# if self.distro == ubuntu:
# os.fchmod(writeDataToFile.fileno(), stat.S_IRUSR | stat.S_IWUSR)
writeDataToFile.close()
if self.remotemysql == 'ON':
command = "sed -i 's|gmysql-host=localhost|gmysql-host=%s|g' %s" % (self.mysqlhost, dnsPath)
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
command = "sed -i 's|gmysql-port=3306|gmysql-port=%s|g' %s" % (self.mysqlport, dnsPath)
install_utils.call(command, self.distro, command, command, 1, 1, os.EX_OSERR)
# Set proper permissions for PowerDNS config
if self.distro == ubuntu:
# Ensure pdns user/group exists
command = 'id -u pdns &>/dev/null || useradd -r -s /usr/sbin/nologin pdns'
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
command = 'chown root:pdns %s' % dnsPath
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
command = 'chmod 640 %s' % dnsPath
install_utils.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
InstallCyberPanel.stdOut("PowerDNS configured!", 1)
except IOError as msg:
logging.InstallLog.writeToFile('[ERROR] ' + str(msg) + " [installPowerDNSConfigurations]")
return 0
return 1
def startPowerDNS(self):
############## Start PowerDNS ######################
self.manage_service('pdns', 'enable')
# During fresh installation, don't start PowerDNS yet
# It will be started after Django migrations create the required tables
InstallCyberPanel.stdOut("PowerDNS enabled for startup.", 1)
InstallCyberPanel.stdOut("Note: PowerDNS will start after database setup is complete.", 1)
logging.InstallLog.writeToFile("PowerDNS enabled but not started - waiting for Django migrations")
# The service will be started later after migrations run
# or manually by the admin after installation completes
def Main(cwd, mysql, distro, ent, serial=None, port="8090", ftp=None, dns=None, publicip=None, remotemysql=None,
mysqlhost=None, mysqldb=None, mysqluser=None, mysqlpassword=None, mysqlport=None):
InstallCyberPanel.mysqlPassword = install_utils.generate_pass()
InstallCyberPanel.mysql_Root_password = install_utils.generate_pass()
file_name = '/etc/cyberpanel/mysqlPassword'
if remotemysql == 'OFF':
if os.access(file_name, os.F_OK):
password = open(file_name, 'r')
InstallCyberPanel.mysql_Root_password = password.readline()
password.close()
else:
password = open(file_name, "w")
password.writelines(InstallCyberPanel.mysql_Root_password)
password.close()
else:
mysqlData = {'remotemysql': remotemysql, 'mysqlhost': mysqlhost, 'mysqldb': mysqldb, 'mysqluser': mysqluser,
'mysqlpassword': mysqlpassword, 'mysqlport': mysqlport}
from json import dumps
writeToFile = open(file_name, 'w')
writeToFile.write(dumps(mysqlData))
writeToFile.close()
if install.preFlightsChecks.debug:
print(open(file_name, 'r').read())
time.sleep(10)
try:
command = 'chmod 640 %s' % (file_name)
install_utils.call(command, distro, '[chmod]',
'',
1, 0, os.EX_OSERR)
command = 'chown root:cyberpanel %s' % (file_name)
install_utils.call(command, distro, '[chmod]',
'',
1, 0, os.EX_OSERR)
except:
pass
if distro == centos:
InstallCyberPanel.mysqlPassword = install_utils.generate_pass()
else:
InstallCyberPanel.mysqlPassword = InstallCyberPanel.mysql_Root_password
installer = InstallCyberPanel("/usr/local/lsws/", cwd, distro, ent, serial, port, ftp, dns, publicip, remotemysql,
mysqlhost, mysqldb, mysqluser, mysqlpassword, mysqlport)
logging.InstallLog.writeToFile('Installing LiteSpeed Web server,40')
installer.installLiteSpeed()
if ent == 0:
installer.changePortTo80()
logging.InstallLog.writeToFile('Installing Optimized PHPs..,50')
installer.installAllPHPVersions()
if ent == 0:
installer.fix_ols_configs()
logging.InstallLog.writeToFile('Installing Sieve for email filtering..,55')
installer.installSieve()
logging.InstallLog.writeToFile('Installing MySQL,60')
installer.installMySQL(mysql)
installer.changeMYSQLRootPassword()
installer.startMariaDB()
if remotemysql == 'OFF':
if distro == ubuntu:
installer.fixMariaDB()
mysqlUtilities.createDatabase("cyberpanel", "cyberpanel", InstallCyberPanel.mysqlPassword, publicip)
if ftp is None:
installer.installPureFTPD()
installer.installPureFTPDConfigurations(mysql)
installer.startPureFTPD()
else:
if ftp == 'ON':
installer.installPureFTPD()
installer.installPureFTPDConfigurations(mysql)
installer.startPureFTPD()
if dns is None:
installer.installPowerDNS()
installer.installPowerDNSConfigurations(InstallCyberPanel.mysqlPassword, mysql)
installer.startPowerDNS()
else:
if dns == 'ON':
installer.installPowerDNS()
installer.installPowerDNSConfigurations(InstallCyberPanel.mysqlPassword, mysql)
installer.startPowerDNS()