File: //usr/share/l.v.e-manager/utils/plesk_nginx_php_sync.py
#!/opt/cloudlinux/venv/bin/python3 -bb
# -*- coding: utf-8 -*-
#
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2019 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
"""
Ensure nginxServePhp is disabled for PHP Selector compatible domains on Plesk.
When PHP Selector is active (domains using CGI/FastCGI handlers), nginx
should proxy PHP requests to Apache, not serve them directly via FPM.
If nginxServePhp remains true, Nginx generates fastcgi_pass directives
pointing to non-existent php-fpm sockets, causing 502/socket errors.
Can be run standalone or called via subprocess by other scripts.
Usage:
plesk_nginx_php_sync.py [--dry-run] [--domain DOMAIN] [-v]
See: CLOS-3894
"""
import argparse
import logging
import subprocess
import sys
import clcommon.cpapi
import cldetectlib
from clcommon import mysql_lib
PLESK_DOMAIN_CLI = "/usr/local/psa/bin/domain"
log = logging.getLogger("plesk_nginx_php_sync")
def _get_selector_compatible_domains():
"""
Return flat set of all PHP Selector compatible domain names on Plesk.
Uses the existing panel-agnostic API via clselect.
"""
from clselect.clselectdomains import get_all_selector_compatible_domains_flat
return get_all_selector_compatible_domains_flat()
def _is_domain_selector_compatible(domain_name):
"""
Check if a single domain is PHP Selector compatible on Plesk.
Uses the canonical check from clselectdomains first, then
falls back to a direct Plesk DB query (handler must not end
with 'fpm', the only mode where nginx should serve PHP directly).
"""
try:
compatible = _get_selector_compatible_domains()
return domain_name in compatible
except Exception:
log.debug(
"Canonical check unavailable, falling back to DB query",
exc_info=True,
)
try:
db_access = clcommon.cpapi.db_access()
connector = mysql_lib.MySQLConnector(
host="localhost",
user=db_access["login"],
passwd=db_access["pass"],
db="psa",
)
query = "SELECT h.php_handler_id FROM hosting h JOIN domains d ON h.dom_id = d.id WHERE d.name = %s LIMIT 1"
with connector.connect() as db:
rows = db.execute_query(query, (domain_name,))
if rows and rows[0][0]:
return not rows[0][0].endswith("fpm")
except Exception:
log.debug(
"Could not determine handler for %s",
domain_name,
exc_info=True,
)
return False
def disable_nginx_serve_php(domain, dry_run=False):
"""
Set nginxServePhp=false for the given domain via Plesk CLI.
Uses --update-web-server-settings (not --update, which silently
ignores -nginx-serve-php). Also triggers web server config regeneration.
Returns True on success, False on error.
"""
cmd = [PLESK_DOMAIN_CLI, "--update-web-server-settings", domain, "-nginx-serve-php", "false"]
if dry_run:
log.info("DRY RUN: would run: %s", " ".join(cmd))
return True
try:
subprocess.check_output(cmd, stderr=subprocess.STDOUT, timeout=60)
log.info("Set nginxServePhp=false for domain: %s", domain)
return True
except subprocess.CalledProcessError as e:
log.warning(
"Failed to update domain %s: %s",
domain,
e.output.decode(errors="replace").strip(),
)
return False
except subprocess.TimeoutExpired:
log.warning("Timeout updating domain %s", domain)
return False
def sync_all(dry_run=False):
"""
Sync nginxServePhp=false for all PHP Selector compatible domains.
Returns (success_count, failure_count).
"""
try:
domains = _get_selector_compatible_domains()
except Exception:
log.error("Failed to get PHP Selector domains", exc_info=True)
return 0, 0
if not domains:
log.info("No PHP Selector compatible domains found")
return 0, 0
log.info("Found %d PHP Selector compatible domain(s)", len(domains))
success = 0
failed = 0
for domain in sorted(domains):
if disable_nginx_serve_php(domain, dry_run=dry_run):
success += 1
else:
failed += 1
return success, failed
def sync_domain(domain, dry_run=False):
"""
Sync nginxServePhp=false for a single domain if it is PHP Selector
compatible (uses CGI/FastCGI handler). Safe to call for any domain —
non-compatible domains are skipped.
Returns True if updated or skipped, False on error.
"""
if not _is_domain_selector_compatible(domain):
log.debug("Domain %s is not PHP Selector compatible, skipping", domain)
return True
return disable_nginx_serve_php(domain, dry_run=dry_run)
def main():
parser = argparse.ArgumentParser(
description="Sync Plesk nginxServePhp=false for PHP Selector domains",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Show what would be done without making changes",
)
parser.add_argument(
"--domain",
type=str,
help="Update a specific domain instead of all",
)
parser.add_argument(
"--verbose",
"-v",
action="store_true",
help="Enable verbose logging",
)
args = parser.parse_args()
logging.basicConfig(
level=logging.DEBUG if args.verbose else logging.INFO,
format="%(levelname)s: %(message)s",
)
if not cldetectlib.is_plesk():
log.info("Not a Plesk server, nothing to do")
return 0
if args.domain:
ok = sync_domain(args.domain, dry_run=args.dry_run)
return 0 if ok else 1
success, failed = sync_all(dry_run=args.dry_run)
log.info("Done: %d updated, %d failed", success, failed)
return 1 if failed > 0 else 0
if __name__ == "__main__":
sys.exit(main())