%PDF- %PDF-
| Direktori : /usr/sbin/ |
| Current File : //usr/sbin/wp-toolkit-clone-awp-settings |
#!/opt/cloudlinux/venv/bin/python3
# -*- coding: utf-8 -*-
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2025 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
import argparse
import json
import logging
import os
import pwd
import subprocess
import sys
from urllib.parse import urlparse
from clwpos.logsetup import setup_logging
from clwpos.optimization_features import Feature
# Set up logger
logger = setup_logging(
caller_name='wp-toolkit-clone-awp-settings',
console_level=logging.INFO,
file_level=logging.DEBUG,
logfile_path='/var/log/clwpos/wp-toolkit-clone.log'
)
def get_wp_toolkit_instance_info(instance_id):
"""Get WP Toolkit instance information
Returns JSON data from WP Toolkit API in the following format:
{
"id": 3,
"siteUrl": "https://us17.com/wp1",
"fullPath": "/var/www/vhosts/us17.com/httpdocs/wp1",
"mainDomainId": 3,
"path": "/httpdocs/wp1",
"ownerId": 4,
"name": "Exquisite Goods",
"version": "6.8.1",
"alive": true,
"stateText": "Working",
... // other fields
}
Key fields used by this script:
- id: WordPress instance ID (for validation)
- siteUrl: Full URL of the WordPress site (used to extract domain and wp_path)
- fullPath: Absolute filesystem path (used to determine site owner)
Args:
instance_id (str|int): WP Toolkit instance ID
Returns:
dict|None: Instance data on success, None on failure
"""
logger.debug(f"Getting WP Toolkit instance info for ID: {instance_id}")
try:
# Simple panel detection - check if Plesk binary exists
if os.path.exists('/usr/sbin/plesk'):
cmd = ['/usr/sbin/plesk', 'ext', 'wp-toolkit']
else:
cmd = ['/usr/local/bin/wp-toolkit']
cmd.extend(['-info', '-instance-id', str(instance_id), '-format', 'json'])
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
instance = json.loads(result.stdout)
if isinstance(instance, dict) and 'id' in instance:
if str(instance.get('id')) == str(instance_id):
return instance
else:
logger.error(f"Instance ID mismatch: expected {instance_id}, got {instance.get('id')}")
return None
else:
logger.error(f"Invalid instance data structure: {type(instance)}")
return None
except subprocess.CalledProcessError as e:
logger.error(f"WP Toolkit command failed with return code {e.returncode}")
logger.debug(f"Command stderr: {e.stderr}")
return None
except json.JSONDecodeError as e:
logger.error(f"Failed to parse WP Toolkit JSON output: {e}")
return None
except Exception as e:
logger.error(f"Unexpected error getting instance info: {e}")
return None
def extract_site_details(instance):
"""Extract domain and wp_path from WP Toolkit instance data"""
if not instance or not isinstance(instance, dict):
logger.error(f"Invalid instance data: {type(instance)}")
return None, None
try:
site_url = instance.get('siteUrl', '')
if not site_url:
logger.error("Missing siteUrl in instance data")
return None, None
# Parse URL properly using standard library
parsed = urlparse(site_url)
domain = parsed.netloc
# Get path and normalize it
wp_path = parsed.path.strip('/')
if not wp_path:
wp_path = '/'
return domain, wp_path
except Exception as e:
logger.error(f"Error extracting site details: {e}")
return None, None
def get_site_owner(instance):
"""Get site owner by examining filesystem ownership using fullPath from WP Toolkit"""
full_path = instance.get('fullPath', '')
domain = instance.get('siteUrl', '').replace('https://', '').replace('http://', '').split('/')[0]
try:
if not full_path or not os.path.exists(full_path):
logger.error(f"Full path does not exist: {full_path}")
return None
stat_info = os.stat(full_path)
owner_uid = stat_info.st_uid
owner = pwd.getpwuid(owner_uid).pw_name
return owner
except Exception as e:
logger.error(f"Error getting site owner: {e}")
return None
def get_awp_features(domain, wp_path, username):
"""Get AccelerateWP features for a site"""
try:
cmd = [
'cloudlinux-awp-user',
'--user', username,
'get',
'--domain', domain,
'--wp-path', wp_path
]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
features_data = json.loads(result.stdout)
return features_data
except subprocess.CalledProcessError as e:
logger.error(f"cloudlinux-awp-user get failed: {e.stderr}")
return None
except json.JSONDecodeError as e:
logger.error(f"Failed to parse AWP features JSON: {e}")
return None
except Exception as e:
logger.error(f"Unexpected error getting AWP features: {e}")
return None
def copy_awp_features(source_features, source_domain, source_wp_path, target_domain, target_wp_path, target_username):
"""Copy AccelerateWP features from source to target"""
if not source_features:
logger.error("No source features data")
return False
if not isinstance(source_features, dict):
logger.error(f"Source features is not a dict: {type(source_features)}")
return False
if 'docroots' not in source_features:
logger.error("Missing 'docroots' key in source features data")
return False
# Normalize wp_path for comparison (both '/' and '' mean root WordPress)
normalized_source_path = '' if source_wp_path == '/' else source_wp_path
# Find the features for the specific WordPress site that is being cloned
wp_features = None
for i, docroot in enumerate(source_features.get('docroots', [])):
# Check if this docroot contains our source domain
docroot_domains = docroot.get('domains', [])
logger.debug(f"Docroot {i}: domains={docroot_domains}")
if source_domain not in docroot_domains:
logger.debug(f"Skipping docroot {i} - does not contain domain {source_domain}")
continue
logger.debug(f"Docroot {i} contains source domain, checking WordPress sites...")
wps = docroot.get('wps', [])
logger.debug(f"Found {len(wps)} WordPress sites in this docroot")
for j, wp_site in enumerate(wps):
site_path = wp_site.get('path', '')
# Normalize site_path for comparison (both '/' and '' mean root WordPress)
normalized_site_path = '' if site_path == '/' else site_path
logger.debug(f" WP site {j}: path='{site_path}' (normalized: '{normalized_site_path}') - comparing with '{normalized_source_path}'")
# Check if this is exactly the WordPress site that is being cloned
if normalized_site_path == normalized_source_path:
wp_features = wp_site.get('features', {})
logger.debug(f"Found source site: {source_domain}:{source_wp_path}")
logger.debug(f"Features available: {list(wp_features.keys()) if wp_features else 'None'}")
break
if wp_features is not None:
break
if wp_features is None:
logger.error(f"Could not find source WordPress site: {source_domain}:{source_wp_path}")
logger.error("Available sites in docroots:")
for i, docroot in enumerate(source_features.get('docroots', [])):
domains = docroot.get('domains', [])
logger.error(f" Docroot {i}: domains={domains}")
for j, wp_site in enumerate(docroot.get('wps', [])):
path = wp_site.get('path', '')
logger.error(f" WP site {j}: path='{path}'")
return False
if not wp_features:
logger.info("No features configured on source site")
return True
features_to_copy = []
# Use centralized feature mapping from Feature class
for feature_name, feature_data in wp_features.items():
if isinstance(feature_data, dict) and feature_data.get('enabled'):
try:
# Use centralized mapping from Feature class
feature_obj = Feature(feature_name)
cli_feature_name = feature_obj.to_interface_name()
features_to_copy.append(cli_feature_name)
except Exception as e:
logger.warning(f"Unknown feature '{feature_name}': {e}")
continue
if not features_to_copy:
logger.info("No features enabled on source site")
return True
logger.info(f"Copying features: {features_to_copy}")
# Enable features on target
success = True
for feature in features_to_copy:
try:
cmd = [
'cloudlinux-awp-user',
'--user', target_username,
'enable',
'--feature', feature,
'--domain', target_domain,
'--wp-path', target_wp_path,
'--ignore-errors'
]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
logger.info(f"Enabled {feature}")
except subprocess.CalledProcessError as e:
logger.error(f"Failed to enable {feature}: {str(e)}")
success = False
except Exception as e:
logger.error(f"Unexpected error enabling {feature}: {e}")
success = False
return success
def main():
parser = argparse.ArgumentParser(description='Copy AccelerateWP settings from parent site to clone')
parser.add_argument('--source-instance-id', required=True, help='Source WP Toolkit instance ID')
parser.add_argument('--target-instance-id', required=True, help='Target WP Toolkit instance ID')
parser.add_argument('--quiet', action='store_true', help='Suppress output messages')
args, unknown = parser.parse_known_args()
# Unknown arguments are normal in some contexts (e.g., WP Toolkit hook for cPanel)
if unknown:
logger.info(f"Unknown arguments: {unknown}")
# Adjust logging level if quiet mode requested
if args.quiet:
logger.setLevel(logging.ERROR)
for handler in logger.handlers:
handler.setLevel(logging.ERROR)
logger.info(f"Starting AWP copy: {args.source_instance_id} → {args.target_instance_id}")
# Get source instance info
source_instance = get_wp_toolkit_instance_info(args.source_instance_id)
if not source_instance:
logger.error("Failed to get source instance information")
sys.exit(1)
# Get target instance info
target_instance = get_wp_toolkit_instance_info(args.target_instance_id)
if not target_instance:
logger.error("Failed to get target instance information")
sys.exit(1)
# Extract site details
source_domain, source_wp_path = extract_site_details(source_instance)
target_domain, target_wp_path = extract_site_details(target_instance)
if not all([source_domain, target_domain]):
logger.error("Failed to extract site details")
sys.exit(1)
# Get site owners
source_username = get_site_owner(source_instance)
target_username = get_site_owner(target_instance)
if not all([source_username, target_username]):
logger.error("Failed to determine site owners")
sys.exit(1)
logger.info(f"Source: {source_domain}:{source_wp_path} ({source_username})")
logger.info(f"Target: {target_domain}:{target_wp_path} ({target_username})")
# Get source AWP features
source_features = get_awp_features(source_domain, source_wp_path, source_username)
if not source_features:
logger.error("Failed to get source AWP features")
sys.exit(1)
# Copy features to target
if copy_awp_features(source_features, source_domain, source_wp_path, target_domain, target_wp_path, target_username):
logger.info("AWP settings copied successfully")
sys.exit(0)
else:
logger.error("Failed to copy AWP settings")
sys.exit(1)
if __name__ == '__main__':
main()