#!/opt/imh-python/bin/python3 """ Python script to configure a CWP server immediately after installation. Unfortunately CWP doesn't provide tools for manual configuration, so this script is result of porting actions from the CWP front end. As such it is subject to change / break unexpectedly due to product changes. Code is relatively defensive of this. Fixes will have to be manually sussed out by visiting the dashboard and parsing out what happens in the browser "Network" tab. """ from cwp_front_api import login import re import json import sys import traceback from socket import gethostname, gethostbyname, gaierror from time import time, sleep from pathlib import Path import click import subprocess from requests.adapters import HTTPAdapter, Retry import requests import distro import os import sqlite3 def license_check(): license_file = Path("/usr/local/cwp/.conf/.cwp_pro.conf") if not license_file.exists() or "1" not in license_file.read_text(): return False return True def refresh_license(): subprocess.check_call( ["/bin/bash", "/scripts/update_cwp"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL, ) # Extra space for web servers to come up sleep(2) def request(sess: requests.Session, method, url, data=None): for _ in range(10): try: response = sess.request(method, url, data=data) return response except ( requests.exceptions.ConnectionError, requests.exceptions.ChunkedEncodingError, ) as e: print(f"Requests gave us a connection error for {url}. Waiting 10s") sleep(10) continue def ensure_license(): if not license_check(): print("License not found, attempting to pull license") refresh_license() if not license_check(): raise Exception("This script will only work on licensed CWP Pro servers") def dump_log(msg): with Path("/tmp/post-provision-log.txt").open("a") as f: f.write(f"{msg}\n") def configure_security(sess, admin_url, ids): """ Set security settings """ def set_modsec_rules(): post_url = f"{admin_url}/loader_ajax.php?ajax=mod_security&acc=update_settings" r = request( sess, "POST", post_url, data=dict( rules="3", # Comodo WAF 'cwaf' engine="On", audit="RelevantOnly", ), ) data = r.json() if data["result"] != "success": raise Exception(f"set_modsec_rules fail: {data}") def disable_modsec_rules(ids): modsec_conf = "/usr/local/apache/modsecurity-cwaf/custom_user.conf" try: existing_rules = set() with open(modsec_conf, "r") as f: for line in f: if line.startswith("SecRuleRemoveById"): existing_rules.add(line.strip()) with open(modsec_conf, "a") as f: for rule_id in ids: rule_line = f"SecRuleRemoveById {rule_id}" if rule_line not in existing_rules: f.write(f"{rule_line}\n") except Exception: pass set_modsec_rules() disable_modsec_rules(ids) def get_system_php_version(sess, admin_url): switcher_url = f"{admin_url}/index.php?module=php_switch_v2" response = request(sess, "GET", switcher_url) if response.status_code != 200: raise Exception(f"get_system_php_version fail: {response.status_code}") data = response.text.splitlines() for line in data: if line.startswith("let current_version = "): version = re.search(r"current_version = '(.+)'", line) if not version: raise Exception( f"get_system_php_version failed to detect current version" ) return version.group(1) def configure_php(sess, admin_url, debug=False): """ Install PHP versions, set PHP settings Return default PHP version """ selected_versions = ["7.4", "8.0", "8.1", "8.2", "8.3"] if distro.name() == "AlmaLinux": # BUG: CWP on AlmaLinux fails to build PHP 8.3 selected_versions.remove("8.3") default_version = "8.2" if debug: # Don't compile every version selected_versions = [default_version] print("Running in debug mode") php_settings = { "post_max_size": "128M", "upload_max_filesize": "128M", "max_execution_time": "300", "memory_limit": "256M", "disable_functions": "exec,passthru,shell_exec,system", } def install_php_versions(versions: dict, fpm: bool): # versions=[{"p":8.2,"v":"8.2.14"},{"p":8.3,"v":"8.3.1"}] v_list = [] for k, v in versions.items(): v_list.append( dict( p=k, v=v, ) ) selector = "phpfpm_selector" if fpm else "php_selector" post_url = f"{admin_url}/loader_ajax.php?ajax={selector}&acc=compile_php_vesion" r = request(sess, "POST", post_url, data=dict(versions=json.dumps(v_list))) data = r.json() if data["result"] != "success": raise Exception(f"install_php_versions fail: {data}") def get_compile_status(fpm: bool, switcher: bool = False): """ Poll PHP compile status. Return None if running Return list of installed versions if completed """ if fpm: selector = "phpfpm_selector" elif not switcher: selector = "php_selector" else: selector = "php_switcher" post_url = ( f"{admin_url}/loader_ajax.php?ajax={selector}&acc=check_compile_status" ) r = request(sess, "POST", post_url) data = r.json() if data["result"] != "success": raise Exception(f"get_compile_status fail: {data}") status = data["status"] if status == "running": return None if status == "stopped": if switcher: return [data["current_version"]] else: versions = data["php_versions_installed"] installed = [v["ver"] for v in versions if v["installed"]] return list(installed) else: raise Exception( f"get_compile_status returned unexpected status: {status}. {data=}" ) def get_php_version_info(fpm: bool): selector = "php_selector3" if fpm else "php_selector2" post_url = f"{admin_url}/index.php?module={selector}" response = request(sess, "GET", post_url) if response.status_code != 200: raise Exception(f"get_php_version_info fail: {response.status_code}") data = response.text.splitlines() php_lines = list( [line for line in data if line.startswith("let php_versions_")] ) if len(php_lines) != 2: # expecting: # let php_versions_installed = # let php_versions_available = raise Exception(f"get_php_version_info failed to detect JSON lines") result = {} for line in php_lines: js_data = re.search(r"JSON.parse\('(.+)'\);", line) if not js_data: raise Exception( f"get_php_version_info failed to read JSON line with php version info" ) as_json = js_data.group(1).replace(r"\/", "/") # un-escape backslashes if line.startswith("let php_versions_installed"): result["installed"] = json.loads(as_json) elif line.startswith("let php_versions_available"): result["available"] = json.loads(as_json) return result def get_latest(item): versions = item["available"] # sort period separated versions versions.sort(key=lambda x: [int(n) for n in x.split(".")], reverse=True) return versions[0] # highest def match_php_versions(versions: dict, fpm: bool): php_versions = get_php_version_info(fpm=fpm) php_install_versions = {} for item in php_versions["available"]: # item = { "ver": "8.3", "available": ["8.3.0", "8.3.1"], "custom_config": false} version = item["ver"] if version not in versions: # skip unselected versions continue already_installed = next( filter( lambda x: x["ver"] == version and x["installed"], php_versions["installed"], ), None, ) if already_installed: # skip already installed versions print(f"PHP {fpm=} version {version} already installed") continue latest = get_latest(item) php_install_versions[version] = latest if not php_install_versions: print(f"Target {versions=} are either already installed or not available") return print(f"Compiling PHP {fpm=} versions. {php_install_versions=}") print("This will take a while...") install_php_versions(php_install_versions, fpm=fpm) install_start = time() while True: compile_status = get_compile_status(fpm=fpm) if compile_status: break sleep(5) print( f"PHP {fpm=} compile completed after {int(time() - install_start)} seconds" ) returned_versions = compile_status for version in php_install_versions.keys(): if version not in returned_versions: raise Exception( f"PHP {fpm=} version {version} not installed. {returned_versions=}" ) set_auto_update(version, fpm=fpm) def set_system_php(version): """ Set system PHP version, which apparently also has to compile PHP. """ php_versions = get_php_version_info(fpm=False) target_version_item = next( filter( lambda x: x["ver"] == version, php_versions["available"], ), None, ) if not target_version_item: raise Exception(f"PHP version {version} not available.") target_v = get_latest(target_version_item) current_version = get_system_php_version(sess, admin_url) if current_version == target_v: print(f"System PHP already set to {target_v}") return get_opts_url = ( f"{admin_url}/loader_ajax.php?ajax=php_switcher&acc=get_php_options" ) r = request( sess, "POST", get_opts_url, data=dict(version=version, minor_version=target_v), ) data = r.json() if data["result"] != "success": raise Exception( f"set_system_php {version=} failed to get php options: {data}" ) # farm out default php options opts = list([opt["name"] for opt in data["options"] if opt["active"]]) print(f"Building system PHP {version=} with {opts=}") print("This will take a while...") switcher_url = ( f"{admin_url}/loader_ajax.php?ajax=php_switcher&acc=save_options_compile" ) r = request( sess, "POST", switcher_url, data=dict( options=json.dumps(opts), type="system", version=version, minor_version=target_v, ), ) data = r.json() if data["result"] != "success": raise Exception( f"set_system_php {version=} failed to start PHP compile: {data}" ) install_start = time() while True: compile_status = get_compile_status(fpm=False, switcher=True) if compile_status: break sleep(5) print( f"System PHP compile completed after {int(time() - install_start)} seconds" ) def set_auto_update(version, fpm: bool): # /loader_ajax.php?ajax=php_selector&acc=auto_update_change selector = "phpfpm_selector" if fpm else "php_selector" post_url = f"{admin_url}/loader_ajax.php?ajax={selector}&acc=auto_update_change" r = request(sess, "POST", post_url, data=dict(version=version, action="enable")) data = r.json() if data["result"] != "success": raise Exception(f"set_auto_update {version=} {fpm=} fail: {data}") def change_php_ini(search, replace): ini_file = Path("/usr/local/php/php.ini") ini_lines = ini_file.read_text(encoding="utf-8").splitlines() for i, line in enumerate(ini_lines): if line.startswith(search): ini_lines[i] = replace ini_file.write_text("\n".join(ini_lines), encoding="utf-8") return raise Exception(f"Could not find {search} in {ini_file}") set_system_php(default_version) match_php_versions(selected_versions, fpm=False) match_php_versions(selected_versions, fpm=True) for setting, value in php_settings.items(): change_php_ini(search=setting, replace=f"{setting} = {value}") return default_version def get_csrf_token(sess, url): response = request(sess, "GET", url) if response.status_code != 200: raise Exception(f"get_csrf_token fail: {response.status_code}") data = response.text.splitlines() token_line = next(filter(lambda x: "name='csrf_token" in x, data), None) if not token_line: raise Exception(f"get_csrf_token failed to detect csrf token") return re.search(r"value='(.+)'", token_line).group(1) def mod_remoteip(): subprocess.check_call( ["/opt/cwprads/cloudflare_apache_config", "--enable"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL, ) # Extra space for web servers to come up sleep(2) def configure_webserver(sess, admin_url, fpm_version): webserver_url = f"{admin_url}/index.php?module=WebServers_manage" webserver_setup = "nginx-apache" version_name = fpm_version.replace(".", "") def set_webserver_settings(csrf_token): settings = dict( csrf_token=csrf_token, action="save_changes", webserver_setup=webserver_setup, ) settings["apache-php-fpm_ver-default"] = version_name settings["nginx-php-fpm_ver-default"] = version_name r = request( sess, "POST", webserver_url, data=settings, ) if r.status_code != 200: raise Exception(f"webserver save_changes fail: {r.status_code}") # we don't get nice json to parse. Just a checked checkbox in html... success = f"value='{webserver_setup}' checked" in r.text if not success: raise Exception( f"webserver save_changes did not return with " f"{webserver_setup} checked in dashboard" ) def set_webserver_conf(csrf_token): """ Set FPM as default and set FPM conf template. """ conf_url = f"{admin_url}/index.php?module=WebServers_conf" settings = dict(action="save_changes", csrf_token=csrf_token, rebuild_all="on") our_settings = { "nginx_template-type-default": "default", "nginx_template-name-default": "default", "nginx-php-fpm_ver-default": version_name, "nginx-php-fpm_ver-template": "default", "apache_template-type-default": "php-fpm", "apache_template-name-default": "all_methods", "apache-php-fpm_ver-default": version_name, "apache-php-fpm_ver-template": "all_methods", "php-fpm_template-name-default": "processes-10", # 10 children } settings = settings | our_settings r = request(sess, "POST", conf_url, data=settings) if ( r.status_code != 200 or "New configuration for WebServers saved!" not in r.text ): raise Exception( f"webserver_conf save_changes did not return success message" ) def set_hostname_redirect(): ip_re = re.compile(r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})") hostname = gethostname() try: host_ip = gethostbyname(hostname) except gaierror: return "Failed to resolve hostname to an IP address. Skipping replacement." conf_file = Path(r"/usr/local/apache/conf.d/system-redirects.conf") if conf_file.exists(): data = conf_file.read_text() try: set_ip = ip_re.search(data)[0] except TypeError: set_ip = "localhost.localdomain" to_replace = host_ip if set_ip == host_ip else set_ip data = data.replace(to_replace, hostname) conf_file.write_text(data) def set_pkg_exclude(): yum_conf = Path("/etc/yum.conf") # symlink to dnf.conf in 8+ data = yum_conf.read_text(encoding="utf-8") if "exclude" in data: if "http*" in data: return from_pat = r"(exclude=)(.*\n)" to_pat = r"\1http* \2" else: from_pat = r"(\[main\]\n)" to_pat = r"\1exclude=http*\n" add_excl = re.sub( rf"{from_pat}", rf"{to_pat}", data, ) yum_conf.write_text(add_excl) csrf_token = get_csrf_token(sess, webserver_url) print(f"Setting webserver to PHP-FPM {fpm_version} with {webserver_setup}") set_webserver_settings(csrf_token) print("Setting default webserver configuration templates") set_webserver_conf(csrf_token) print("Setting system hostname redirects") set_hostname_redirect() set_pkg_exclude() mod_remoteip() def configure_dovecot(): """ Adds zlib plugin to Dovecot config after provision """ line_num = 0 lines = [] if os.path.exists("/etc/dovecot/dovecot.conf"): with open("/etc/dovecot/dovecot.conf", encoding="utf-8") as config: for line in config.read().splitlines(): lines.append(line) pattern = r"^(\s*mail_plugins\s*=\s*[^#]+)$" if re.search(pattern, lines[line_num]) and "zlib" not in line: lines[line_num] = re.sub(rf"{pattern}", r"\1 zlib", line) line_num += 1 with open("/etc/dovecot/new_dovecot.conf", "w", encoding="utf-8") as new_config: new_config.writelines([f"{line}\n" for line in lines]) try: os.rename("/etc/dovecot/dovecot.conf", "/etc/dovecot/old_dovecot.conf") os.rename("/etc/dovecot/new_dovecot.conf", "/etc/dovecot/dovecot.conf") except Exception as e: print(e) else: print("Unable to update Dovecot!: No config file found!") def configure_dovecot_ssl(): """ Replace self-signed Dovecot SSL with LE hostname cert. """ dc_ssl_conf = Path("/etc/dovecot/conf.d/10-ssl.conf") if dc_ssl_conf.exists(): ssl_new = re.sub( '(ssl_cert = <)(/etc/pki/dovecot/certs/dovecot.pem\n)(ssl_key = <)(/etc/pki/dovecot/private/dovecot.pem\n)', r'\1/etc/pki/tls/certs/hostname.cert\n\3/etc/pki/tls/private/hostname.key\n', dc_ssl_conf.read_text(encoding="utf-8"), ) with open("/etc/dovecot/conf.d/10-ssl.conf_new", "w", encoding="utf-8") as conf_new: conf_new.write(ssl_new) dc_ssl_conf.rename(f"{dc_ssl_conf}_old") Path.rename( Path("/etc/dovecot/conf.d/10-ssl.conf_new"), dc_ssl_conf, ) subprocess.check_call( ['systemctl', 'restart', 'dovecot'], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL, ) else: print(f"Unable to update Dovecot SSL! File {dc_ssl_conf} not found!") def configure_mailserver(sess, admin_url): mailserver_url = f"{admin_url}/index.php?module=postfix_manager" csrf_token = get_csrf_token(sess, mailserver_url) def get_mailserver_settings(text): """Parse mailserver settings from html text.""" for line in text.splitlines(): # There is a single line that defines the checkboxes. if "name='antispam' value='antispam'" in line: matches = re.findall( r"input type='checkbox' name='([^']+)' value='\1' checked", line, ) return matches def set_mailserver_settings(csrf_token): hostname = gethostname() # antispam = ClamAV, Amavis & Spamassassin, Requires 2Gb+ RAM # rdns_chk = Drop all emails if no rdns/ptr # dkim_spf = Installs DKIM & SPF, enables DKIM for New Accounts and Domains # policyd = Install policyd, hourly limit per domain req_settings = ["antispam", "rdns_chk", "dkim_spf", "policyd"] settings = dict( csrf_token=csrf_token, action="rebuild_configuration", ifpost="yes", hostname=hostname, domain=hostname.split(".", 1)[-1], ) for s in req_settings: settings[s] = s r = request( sess, "POST", mailserver_url, data=settings, ) if r.status_code != 200: raise Exception(f"mailserver save_changes fail: {r.status_code}") returned_settings = get_mailserver_settings(r.text) if returned_settings != req_settings: if ( "rDNS/PTR match check = FAILED, check with your hosting provider" in r.text ): print("Detected rDNS/PTR match check failed") dump_log(f"---\n{r.text}\n---") raise Exception( f"Parsed mailserver settings are {returned_settings} and not {req_settings}" ) print( f"Mailserver settings updated to {returned_settings} with hostname {hostname}" ) set_mailserver_settings(csrf_token) def configure_antispam(sess, admin_url): antispam_url = f"{admin_url}/index.php?module=antispam" csrf_token = get_csrf_token(sess, antispam_url) def set_antispam_settings(csrf_token): settings = dict( ifpost="yes", csrf_token=csrf_token, action="install_spamhaus", ) r = request( sess, "POST", antispam_url, data=settings, ) if r.status_code != 200: raise Exception(f"mailserver save_changes fail: {r.status_code}") lines = r.text.splitlines() for line in lines: if "SpamHause protection is" in line and "Active" in line: return dump_log(f"---\n{r.text}\n---") raise Exception(f"antispam save_changes did not return with SpamHause active") set_antispam_settings(csrf_token) print(f"Antispam RBL is enabled") def configure_spamassassin(): subprocess.check_call( ['/opt/cwprads/configure_sa_cwp.py'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) def configure_email_subjects_in_maillog(): """ Log email subjects in mail log """ header_checks = Path("/etc/postfix/header_checks") postfix_file = Path("/etc/postfix/main.cf") pf_config = postfix_file.read_text(encoding="utf-8") if "header_checks" in pf_config: print(f"Found 'header_checks' in main.cf skipping email subjects change") return line = "header_checks = regexp:/etc/postfix/header_checks" postfix_file.write_text( pf_config + f"\n{line}\n", encoding="utf-8", ) mod_line = "/^Subject:/ WARN" checks_file_content = "" if header_checks.exists(): checks_file_content = header_checks.read_text(encoding="utf-8") + "\n" if mod_line not in checks_file_content: checks_file_content += f"{mod_line}" header_checks.write_text(checks_file_content, encoding="utf-8") print(f"Enabled email header printing in mail log") def mount_retries(session): """ Enable HTTP request retries in case the server drops us out randomly """ adapter = HTTPAdapter( max_retries=Retry( total=10, backoff_factor=10, ) ) session.mount("http://", adapter) session.mount("https://", adapter) def configure_psacct(): """ Enable and start psacct service after provisioning """ subprocess.check_call( ["systemctl", "enable", "psacct"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL, ) subprocess.check_call( ["systemctl", "start", "psacct"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL, ) def configure_packages(sess, admin_url): reseller_pkg_settings = { 'disk_quota': '-1', 'bandwidth': '-1', 'ftp_accounts': '-1', 'email_accounts': '-1', 'email_lists': '-1', 'databases': '-1', 'sub_domains': '-1', 'parked_domains': '-1', 'addons_domains': '-1', 'hourly_emails': '350', 'reseller': '1', 'accounts': '-1', 'cgroups': 'undefined', 'nproc': '450', 'apache_nproc': '60', 'inode': '0', 'nodejs_apps': '1', } # Default for users default_pkg_settings = { 'disk_quota': '20000', # in megabytes 'bandwidth': '100000', 'ftp_accounts': '10', 'email_accounts': '-1', 'email_lists': '-1', 'databases': '10', 'sub_domains': '10', 'parked_domains': '10', 'addons_domains': '10', 'hourly_emails': '250', 'reseller': '', # unset 'accounts': '', # unset 'update_users': '0', 'cgroups': 'undefined', 'nproc': '250', 'apache_nproc': '60', 'inode': '0', 'nofile': '150', 'nodejs_apps': '1' } def update_package(id, pkg_name, features): post_url = ( f"{admin_url}/loader_ajax.php?ajax=packages&acc=save" ) r = request( sess, "POST", post_url, data=dict( id=id, package_name = pkg_name ) | features, ) if r.text != "1" or r.status_code != 200: raise Exception(f"update_package fail: {r.text}") def add_package(pkg_name, settings): post_url = ( f"{admin_url}/loader_ajax.php?ajax=packages&acc=add" ) r = request( sess, "POST", post_url, data=dict( id=id, package_name = pkg_name, ) | settings, ) if r.text != "1" or r.status_code != 200: raise Exception(f"add_package fail: {r.text}") update_package(1, "default", default_pkg_settings) add_package("Primary Reseller", reseller_pkg_settings) def configure_feature_lists(sess, admin_url): default_features = [ "settings", "crontab", "processlist", "phpini_editor", "phpselector", "notification_center", "mod_security", "statistics", "file_management", "files_system_lock", "clam", "scan_home", "scan_web", "scan_email", "scan_custom", "filemanager", "ftp_accounts", "backups", "backup_manager", "automatic_backup", "restore_backup", "disk_usage", "protected_directory", "error_log", "fix_acc_perm", "domain", "domains", "subdomains", "letsencrypt", "sslwizard", "nodejs_manager", "redirect", "vdomaincache", "databases", "pma", "mysql_manager", "process_list_my", "emails_accounts", "email_accounts", "forwarders_email", "mail_forwarders", "mail_forwarders_pipe", "mail_autoreply", "email_filters", "email_filters_tab", "filters_templates", "roundcube", "mail_routing", "email_importer", "dns_functions", "dns_zone_editor", "softaculous", "addons_wp", # wordpress "addons_dp", # drupal "addons_whc", # wmhcs ] reseller_features = default_features.copy() + [ "reseller", "res_modsecurity", "addons", "res_firewall", "res_nameserver", "res_feactures", "res_branding", "res_api", ] def get_feature_list_count(): sqlite_uri = "file:/usr/local/cwp/.conf/feature.db?mode=ro&nolock=1" try: with sqlite3.connect(sqlite_uri, uri=True) as con: return con.execute("select count(*) from configuration").fetchone()[0] except Exception: # the db might be undefined/not created return 0 def set_feature_list(name: str, pkg_name: str, modules: list): post_url = ( f"{admin_url}/loader_ajax.php?ajax=themes_lang&acc=feature_manager&op=save" ) r = request( sess, "POST", post_url, data=dict( name=name, tipo=1, acction=pkg_name, module=", ".join(modules), ), ) if r.status_code != 200: raise Exception(f"set_feature_list fail: {r.text}") if get_feature_list_count() > 0: print("Feature list already configured. Skipping feature configuration") print("Ideally this message never fires, please investigate.") return feature_list_count = 2 set_feature_list("default", "default", default_features) set_feature_list("Reseller", "Primary Reseller", reseller_features) count = get_feature_list_count() if count < feature_list_count: raise Exception(f"Found {count=} features in feature list. Expected {feature_list_count=}") def hotfix_panel_probs(): # T3O2-10607 # This changeset fixes issues preventing the admin dash loading correctly # and user panels from logging in # The features db also comes through unconfigured so we fix this so we can add to it. subprocess.check_call(["/bin/bash", "/opt/cwprads/theme-lang-features-hotfix"]) @click.command( help="Preconfigure CWP. This script will clobber " "existing configuration, and so it is only intended for use " "during provisioning" ) @click.option( "--all/--no-all", default=True, help="Configure all options", is_flag=True ) @click.option( "--security", default=False, help="Configure security (modsec)", is_flag=True, ) @click.option("--php", default=False, help="Configure php", is_flag=True) @click.option("--mail", default=False, help="Configure mailserver", is_flag=True) @click.option("--antispam", default=False, help="Configure antispam", is_flag=True) @click.option("--features", default=False, help="Configure feature lists", is_flag=True) @click.option("--packages", default=False, help="Configure packages", is_flag=True) @click.option( "--webserver-php", is_flag=True, help="Configure webserver with default PHP version", ) @click.option("--debug", default=False, help="Configure debug mode", is_flag=True) @click.option( "--ids", multiple=True, type=int, default=(218500,), help="List of rule IDs to disable", ) def main(all, security, php, mail, antispam, webserver_php, ids, features, packages, debug=False): if all: # by default, do everything security = True php = True mail = True antispam = True webserver_php = True features = True packages = True with login() as (sess, admin_url): mount_retries(sess) ensure_license() configure_psacct() hotfix_panel_probs() if security: configure_security(sess, admin_url, ids) if php: print("--- Configuring PHP") default_php = configure_php(sess, admin_url, debug) else: default_php = get_system_php_version(sess, admin_url) vsplit = default_php.split(".") default_php = f"{vsplit[0]}.{vsplit[1]}" # decode major if webserver_php: print("--- Configuring webserver with PHP-FPM") configure_webserver(sess, admin_url, default_php) if mail: print("--- Configuring mail server") configure_mailserver(sess, admin_url) configure_email_subjects_in_maillog() configure_dovecot_ssl() configure_dovecot() if antispam: print("--- Configuring antispam/SpamHaus RBL") configure_antispam(sess, admin_url) print("--- Configuring SpamAssassin") configure_spamassassin() if packages: print("--- Configuring packages") configure_packages(sess, admin_url) if features: print("--- Configuring feature lists") configure_feature_lists(sess, admin_url) if __name__ == "__main__": try: main() except Exception as e: traceback.print_exc() print("This means provisioning has failed") sys.exit(1)