#!/bin/bash pid=$$ script_dir=$(dirname "$0") timestamp="$(date +'%Y-%m-%d_%H-%M-%S')" #used by log file name start_time=$(date +%s) #used to calculate elapsed time at the end set -eo pipefail ############################################################### # HELPER FUNCTIONS usage() { echo "Usage: $0 --backup-location --plan-name [--dry-run]" echo "" echo "Example: $0 --backup-location /home/backup-7.29.2024_13-22-32_stefan.tar.gz --plan-name default_plan_nginx --dry-run" exit 1 } log() { local message="$1" local timestamp=$(date +'%Y-%m-%d %H:%M:%S') echo "[$timestamp] $message" | tee -a "$log_file" } DEBUG=true # Set this to true to enable debug logging debug_log() { if [ "$DEBUG" = true ]; then log "DEBUG: $1" fi } handle_error() { log "FATAL ERROR: An error occurred in function '$1' on line $2" cleanup exit 1 } trap 'handle_error "${FUNCNAME[-1]}" "$LINENO"' ERR cleanup() { log "Cleaning up temporary files and directories" rm -rf "$backup_dir" } define_data_and_log() { local backup_location="" local plan_name="" DRY_RUN=false # Parse command-line arguments while [ "$1" != "" ]; do case $1 in --backup-location ) shift backup_location=$1 ;; --plan-name ) shift plan_name=$1 ;; --dry-run ) DRY_RUN=true ;; --post-hook ) shift post_hook=$1 ;; * ) usage esac shift done # Validate required parameters if [ -z "$backup_location" ] || [ -z "$plan_name" ]; then usage fi # Format log file and ensure log directory exists base_name="$(basename "$backup_location")" base_name_no_ext="${base_name%.*}" local log_dir="/var/log/openpanel/admin/imports" mkdir -p "$log_dir" log_file="$log_dir/${base_name_no_ext}_$(date +'%Y-%m-%d_%H-%M-%S').log" # Run the main function and pass plan_name echo "Import started, log file: $log_file" main "$plan_name" } command_exists() { command -v "$1" >/dev/null 2>&1 } install_dependencies() { log "Checking and installing dependencies..." install_needed=false # needed commands declare -A commands=( ["tar"]="tar" ["parallel"]="parallel" ["rsync"]="rsync" ["unzip"]="unzip" ["jq"]="jq" ["mysql"]="mysql-client" ["wget"]="wget" ["curl"]="curl" ) for cmd in "${!commands[@]}"; do if ! command_exists "$cmd"; then install_needed=true break fi done # If installation is needed, update package list and install missing packages if [ "$install_needed" = true ]; then log "Updating package manager..." # Hold kernel packages to prevent upgrades sudo apt-mark hold linux-image-generic linux-headers-generic # Update package list without upgrading sudo apt-get update -y >/dev/null 2>&1 for cmd in "${!commands[@]}"; do if ! command_exists "$cmd"; then log "Installing ${commands[$cmd]}" # Install package without upgrading or installing recommended packages sudo apt-get install -y --no-upgrade --no-install-recommends "${commands[$cmd]}" >/dev/null 2>&1 fi done # Unhold kernel packages sudo apt-mark unhold linux-image-generic linux-headers-generic log "Dependencies installed successfully." else log "All required dependencies are already installed." fi } get_server_ipv4(){ # Get server ipv4 from ip.openpanel.co or ifconfig.me new_ip=$(curl --silent --max-time 2 -4 https://ip.openpanel.co || wget --timeout=2 -qO- https://ip.openpanel.co || curl --silent --max-time 2 -4 https://ifconfig.me) # if no internet, get the ipv4 from the hostname -I if [ -z "$new_ip" ]; then new_ip=$(ip addr|grep 'inet '|grep global|head -n1|awk '{print $2}'|cut -f1 -d/) fi } validate_plan_exists(){ if ! opencli plan-list --json | grep -qw "$plan_name"; then log "FATAL ERROR: Plan name '$plan_name' does not exist." exit 1 fi } ############################################################### # MAIN FUNCTIONS # CHECK EXTENSION AND DETERMINE SIZE check_if_valid_cp_backup(){ local backup_location="$1" ARCHIVE_SIZE=$(stat -c%s "$backup_location") # Identify the backup type local backup_filename=$(basename "$backup_location") extraction_command="" case "$backup_filename" in cpmove-*.tar.gz) log "Identified cpmove backup" extraction_command="tar -xzf" EXTRACTED_SIZE=$(($ARCHIVE_SIZE * 2)) ;; backup-*.tar.gz) log "Identified full or partial cPanel backup" extraction_command="tar -xzf" EXTRACTED_SIZE=$(($ARCHIVE_SIZE * 2)) ;; *.tar.gz) log "Identified gzipped tar backup" extraction_command="tar -xzf" EXTRACTED_SIZE=$(($ARCHIVE_SIZE * 2)) ;; *.tgz) log "Identified tgz backup" extraction_command="tar -xzf" EXTRACTED_SIZE=$(($ARCHIVE_SIZE * 3)) ;; *.tar) log "Identified tar backup" extraction_command="tar -xf" EXTRACTED_SIZE=$(($ARCHIVE_SIZE * 3)) ;; *.zip) log "Identified zip backup" extraction_command="unzip" EXTRACTED_SIZE=$(($ARCHIVE_SIZE * 3)) ;; *) log "FATAL ERROR: Unrecognized backup format: $backup_filename" exit 1 ;; esac } # CHECK DISK USAGE check_if_disk_available(){ TMP_DIR="/tmp" HOME_DIR="/home" # Get available space in /tmp and home directories in bytes AVAILABLE_TMP=$(df --output=avail "$TMP_DIR" | tail -n 1) AVAILABLE_HOME=$(df --output=avail "$HOME_DIR" | tail -n 1) AVAILABLE_TMP=$(($AVAILABLE_TMP * 1024)) AVAILABLE_HOME=$(($AVAILABLE_HOME * 1024)) # Check if there's enough space if [[ $AVAILABLE_TMP -ge $EXTRACTED_SIZE && $AVAILABLE_HOME -ge $EXTRACTED_SIZE ]]; then log "There is enough disk space to extract the archive and copy it to the home directory." else log "FATAL ERROR: Not enough disk space." if [[ $AVAILABLE_TMP -lt $EXTRACTED_SIZE ]]; then log "Insufficient space in the '/tmp' partition." log "Available: $AVAILABLE_TMP - Needed: $EXTRACTED_SIZE" fi if [[ $AVAILABLE_HOME -lt $EXTRACTED_SIZE ]]; then log "Insufficient space in the '/home' directory." log "Available: $AVAILABLE_HOME - Needed: $EXTRACTED_SIZE" fi exit 1 fi } # EXTRACT extract_cpanel_backup() { backup_location="$1" backup_dir="$2" log "Extracting backup from $backup_location to $backup_dir" mkdir -p "$backup_dir" # Extract the backup if [ "$extraction_command" = "unzip" ]; then $extraction_command "$backup_location" -d "$backup_dir" else $extraction_command "$backup_location" -C "$backup_dir" fi if [ $? -eq 0 ]; then log "Backup extracted successfully." else log "FATAL ERROR: Backup extraction failed." cleanup exit 1 fi # Handle nested archives (common in some cPanel backups) for nested_archive in "$backup_dir"/*.tar.gz "$backup_dir"/*.tgz; do if [ -f "$nested_archive" ]; then log "Found nested archive: $nested_archive" tar -xzf "$nested_archive" -C "$backup_dir" rm "$nested_archive" # Remove the nested archive after extraction fi done } # LOCATE FILES IN EXTRACTED BACKUP locate_backup_directories() { log "Locating important files in the extracted backup" if [ -d "$real_backup_files_path/homedir" ]; then homedir="$real_backup_files_path/homedir" log "Home directory: $homedir" else log "ERROR: Home directory not found in backup." exit 1 fi if [ -d "$real_backup_files_path/mysql" ]; then mysqldir="$real_backup_files_path/mysql" log "MySQL directory: $mysqldir" else log "ERROR: MySQL directory not found in backup." exit 1 fi if [ -f "$real_backup_files_path/mysql" ]; then log "MySQL grants: $real_backup_files_path/mysql" else log "MySQL grants file not found." fi if [ -f "$real_backup_files_path/cp/$cpanel_username" ]; then cpconfig="$real_backup_files_path/cp/$cpanel_username" log "cPanel configuration: $cpconfig" else log "ERROR: cPanel configuration file not found." exit 1 fi log "Backup directories located successfully" } # CPANEL BACKUP METADATA parse_cpanel_metadata() { log "Starting to parse cPanel metadata..." cp_file="${real_backup_files_path}/cp/${cpanel_username}" log "DEBUG: Attempting to parse metadata from file: $cp_file" if [ ! -f "$cp_file" ]; then log "WARNING: cp file $cp_file not found. Using default values." main_domain="" cpanel_email="" php_version="inherit" else # Function to get value from cp file with default get_cp_value() { local key="$1" local default="$2" local value value=$(grep "^$key=" "$cp_file" | cut -d'=' -f2-) if [ -z "$value" ]; then echo "$default" else echo "$value" fi } main_domain=$(get_cp_value "DNS" "") cpanel_email=$(get_cp_value "CONTACTEMAIL" "") [ -z "$cpanel_email" ] && cpanel_email=$(get_cp_value "CONTACTEMAIL2" "") [ -z "$cpanel_email" ] && cpanel_email=$(get_cp_value "EMAIL" "") # Check for CloudLinux PHP Selector cfg_file="${real_backup_files_path}/homedir/.cl.selector/defaults.cfg" if [ -f "$cfg_file" ]; then php_version=$(grep '^php\s*=' "$cfg_file" | awk -F '=' '{print $2}' | tr -d '[:space:]') [ -z "$php_version" ] && php_version="inherit" else php_version="inherit" fi # Additional metadata ip_address=$(get_cp_value "IP" "") plan=$(get_cp_value "PLAN" "default") max_addon=$(get_cp_value "MAXADDON" "0") max_ftp=$(get_cp_value "MAXFTP" "unlimited") max_sql=$(get_cp_value "MAXSQL" "unlimited") max_pop=$(get_cp_value "MAXPOP" "unlimited") max_sub=$(get_cp_value "MAXSUB" "unlimited") log "Additional metadata parsed:" log "IP Address: $ip_address" log "Plan: $plan" log "Max Addon Domains: $max_addon" log "Max FTP Accounts: $max_ftp" log "Max SQL Databases: $max_sql" log "Max Email Accounts: $max_pop" log "Max Subdomains: $max_sub" fi # Ensure we have at least an empty string for each variable main_domain="${main_domain:-}" cpanel_email="${cpanel_email:-}" php_version="${php_version:-inherit}" log "cPanel metadata parsed:" log "Main Domain: ${main_domain:-Not found}" log "Email: ${cpanel_email:-Not found}" log "PHP Version: $php_version" log "Finished parsing cPanel metadata." } # CHECK BEFORE EXPORT check_if_user_exists(){ backup_filename=$(basename "$backup_location") cpanel_username="${backup_filename##*_}" cpanel_username="${cpanel_username%%.*}" log "Username: $cpanel_username" local existing_user="" if opencli user-list --json > /dev/null 2>&1; then existing_user=$(opencli user-list --json | jq -r ".[] | select(.username == \"$cpanel_username\") | .id") fi if [ -z "$existing_user" ]; then log "Username $cpanel_username is available, starting import.." else log "FATAL ERROR: $cpanel_username already exists." exit 1 fi } # CREATE NEW USER create_new_user() { local username="$1" local password="$2" local email="$3" local plan_name="$4" if [ "$DRY_RUN" = true ]; then log "DRY RUN: Would create user $username with password $password, email $email, and plan $plan_name" return fi # Validate the parameters if [ -z "$username" ] || [ -z "$password" ] || [ -z "$email" ] || [ -z "$plan_name" ]; then log "ERROR: Missing required parameters. Username, password, email, and plan must be provided." return fi # Log the creation attempt log "Attempting to create user $username with email $email, plan $plan_name, and password $password" # Construct the correct opencli command local cli_command="opencli user-add $username $password $email $plan_name" # Log the exact command that will be run log "Running command: $cli_command" # Execute the OpenCLI command create_user_command=$($cli_command 2>&1) # Process the output and log it while IFS= read -r line; do log "$line" done <<< "$create_user_command" # Check if the user was added successfully if echo "$create_user_command" | grep -q "Successfully added user"; then log "User $username was added successfully." else log "ERROR: Failed to create user $username. Command output: $create_user_command" exit 1 fi } # PHP VERSION restore_php_version() { local php_version="$1" if [ "$DRY_RUN" = true ]; then log "DRY RUN: Would check/install PHP version $php_version for user $cpanel_username" return fi # Check if php_version is "inherit" if [ "$php_version" == "inherit" ]; then log "PHP version is set to inherit. No changes will be made." else log "Checking if current PHP version installed matches the version from backup" local current_version=$(opencli php-default_php_version "$cpanel_username" | sed 's/Default PHP version for user.*: //') if [ "$current_version" != "$php_version" ]; then local installed_versions=$(opencli php-enabled_php_versions "$cpanel_username") if ! echo "$installed_versions" | grep -q "$php_version"; then log "Default PHP version $php_version from backup is not present in the container, installing.." output=$(opencli php-install_php_version "$cpanel_username" "$php_version" 2>&1) while IFS= read -r line; do log "$line" done <<< "$output" #SET AS DEFAULT PHP VERSION log "Setting newly installed PHP $php_version as the default version for all new domains." output=$(opencli php-default_php_version "$cpanel_username" --update "$php_version" 2>&1) while IFS= read -r line; do log "$line" done <<< "$output" fi else log "Default PHP version in backup file ($php_version) matches the installed PHP version: ($current_version) " fi fi } # PHPMYADMIN grant_phpmyadmin_access() { local username="$1" if [ "$DRY_RUN" = true ]; then log "DRY RUN: Would grant phpMyAdmin access to all databases for user $username" return fi log "Granting phpMyAdmin access to all databases for user $username" # https://github.com/stefanpejcic/OpenPanel/blob/148b5e482f7bde4850868ba5cf85717538770882/docker/apache/phpmyadmin/pma.php#L13C44-L13C54 phpmyadmin_user="phpmyadmin" sql_command="GRANT ALL ON *.* TO 'phpmyadmin'@'localhost'; FLUSH PRIVILEGES;" grant_commands=$(docker exec $username mysql -N -e "$sql_command") log "Access granted to phpMyAdmin user for all databases of $username" } # MYSQL restore_mysql() { local mysql_backup_dir="$1" local mysql_grants_file="$real_backup_files_path/mysql" log "Starting MySQL database restoration..." log "MySQL backup directory: $mysql_backup_dir" if [ ! -d "$mysql_backup_dir" ]; then log "No MySQL backup directory found at $mysql_backup_dir." return fi # Enable nullglob to handle the case when no .sql files are found shopt -s nullglob sql_files=("$mysql_backup_dir"/*.sql) shopt -u nullglob if [ ${#sql_files[@]} -eq 0 ]; then log "No SQL files found in $mysql_backup_dir." else for sql_file in "${sql_files[@]}"; do db_name=$(basename "$sql_file" .sql) log "Restoring database: $db_name from $sql_file" # Drop the database if it exists log "Dropping database $db_name if it exists..." if mysql -e "DROP DATABASE IF EXISTS \`$db_name\`;" 2>&1 | while read -r line; do log "$line"; done; then log "Database $db_name dropped (if it existed)." else log "ERROR: Failed to drop database $db_name." continue fi # Create the database log "Creating database $db_name..." if mysql -e "CREATE DATABASE \`$db_name\`;" 2>&1 | while read -r line; do log "$line"; done; then log "Database $db_name created successfully." else log "ERROR: Failed to create database $db_name." continue fi # Import the SQL file log "Importing SQL file $sql_file into database $db_name..." if mysql "$db_name" < "$sql_file" 2>&1 | while read -r line; do log "$line"; done; then log "Database $db_name restored successfully." else log "ERROR: Failed to import SQL file into database $db_name." continue fi done fi # Restore MySQL users and grants from mysql grants file if available if [ -f "$mysql_grants_file" ]; then log "Restoring MySQL users and grants from $mysql_grants_file" # Check MySQL version to adjust grant file syntax if necessary mysql_version=$(mysql -V | awk '{print $5}' | awk -F'.' '{print $1"."$2}') log "Detected MySQL version: $mysql_version" temp_grants_file="/tmp/processed_mysql_grants.sql" if (( $(echo "$mysql_version >= 8.0" | bc -l) )); then log "Modifying grants file for MySQL 8.0 compatibility..." # Replace 'IDENTIFIED BY PASSWORD' with 'IDENTIFIED WITH mysql_native_password AS' sed "s/IDENTIFIED BY PASSWORD/IDENTIFIED WITH mysql_native_password AS/g" "$mysql_grants_file" > "$temp_grants_file" else log "Using grants file as-is for MySQL version < 8.0" cp "$mysql_grants_file" "$temp_grants_file" fi # Execute the grants file if mysql < "$temp_grants_file" 2>&1 | while read -r line; do log "$line"; done; then log "MySQL users and grants restored successfully." else log "ERROR: Failed to restore MySQL users and grants." fi # Remove the temporary grants file rm -f "$temp_grants_file" else log "No MySQL grants file found at $mysql_grants_file." fi log "MySQL database restoration completed." } # SSL CACHE refresh_ssl_file() { local username="$1" if [ "$DRY_RUN" = true ]; then log "DRY RUN: Would refresh SSL file for user $username" return fi log "Creating a list of SSL certificates for user interface" opencli ssl-user "$cpanel_username" } # SSL CERTIFICATES restore_ssl() { local username="$1" if [ "$DRY_RUN" = true ]; then log "DRY RUN: Would restore SSL certificates for user $username" return fi log "Restoring SSL certificates for user $username" if [ -d "$real_backup_files_path/ssl" ]; then for cert_file in "$real_backup_files_path/ssl"/*.crt; do local domain=$(basename "$cert_file" .crt) local key_file="$real_backup_files_path/ssl/$domain.key" if [ -f "$key_file" ]; then log "Installing SSL certificate for domain: $domain" opencli ssl install --domain "$domain" --cert "$cert_file" --key "$key_file" else log "SSL key file not found for domain: $domain" fi done # Refresh the SSL file after restoring certificates refresh_ssl_file "$username" else log "No SSL certificates found to restore" fi } # SSH KEYS restore_ssh() { local username="$1" if [ "$DRY_RUN" = true ]; then log "DRY RUN: Would restore SSH access for user $username" return fi log "Restoring SSH access for user $username" local shell_access=$(grep -oP 'shell: \K\S+' "$real_backup_files_path/userdata/main") if [ "$shell_access" == "/bin/bash" ]; then opencli user-ssh enable "$username" if [ -f "$real_backup_files_path/.ssh/id_rsa.pub" ]; then mkdir -p "/home/$username/.ssh" cp "$real_backup_files_path/.ssh/id_rsa.pub" "/home/$username/.ssh/authorized_keys" chown -R "$username:$username" "/home/$username/.ssh" fi fi } # DNS ZONES restore_dns_zones() { log "Restoring DNS zones for user $cpanel_username" #domain_file="$real_backup_files_path/userdata/$domain" #domain=$(basename "$domain_file") if [ "$DRY_RUN" = true ]; then log "DRY RUN: Would restore DNS zones for user $cpanel_username" return fi if [ -d "$real_backup_files_path/dnszones" ]; then for zone_file in "$real_backup_files_path/dnszones"/*; do local zone_name=$(basename "${zone_file%.db}") # Check if the destination zone file exists, if not, it was probably a subdomain that had no dns zone and if [ ! -f "/etc/bind/zones/${zone_name}.zone" ]; then log "DNS zone file /etc/bind/zones/${zone_name}.zone does not exist. Skipping import for $zone_name." continue else log "Importing DNS zone: $zone_name" fi old_ip=$(grep -oP 'IP=\K[0-9.]+' ${real_backup_files_path}/cp/$cpanel_username) if [ -z "$old_ip" ]; then log "WARNING: old server ip address not detected in file ${real_backup_files_path}/cp/$cpanel_username - records will not be automatically updated to new ip address." else log "Replacing old server IP: $old_ip with new IP: $new_ip in DNS zone file for domain: $zone_name" sed -i "s/$old_ip/$new_ip/g" $zone_file fi # Temporary files to store intermediate results temp_file_of_original_zone=$(mktemp) temp_file_of_created_zone=$(mktemp) # Remove all lines after the last line that starts with '@' log "Editing original zone for domain $zone_name to temporary file: $temp_file_of_original_zone" awk '/^@/ { found=1; last_line=NR } { if (found && NR > last_line) exit } { print }' "$zone_file" > "$temp_file_of_original_zone" # Remove all lines from the beginning until the line that has 'NS' and including that line log "Editing created zone for domain $zone_name to temporary file: $temp_file_of_created_zone" awk '/NS/ { found=1; next } found { print }' "/etc/bind/zones/${zone_name}.zone" > "$temp_file_of_created_zone" # Append the processed second file to the first log "Merging the DNS zone records from $temp_file_of_created_zone with $temp_file_of_original_zone" cat "$temp_file_of_created_zone" >> "$temp_file_of_original_zone" # Move the merged content to the final file log "Replacing the created zone /etc/bind/zones/${zone_name}.zone with updated records." mv "$temp_file_of_original_zone" "/etc/bind/zones/${zone_name}.zone" # Clean up rm "$temp_file_of_created_zone" log "DNS zone file for $zone_name has been imported." done else log "No DNS zones found to restore" fi } # HOME DIR restore_files() { log "Restoring files ($backup_size) to /home/$cpanel_username/" log "real_backup_files_path is set to: $real_backup_files_path" if [ ! -d "/home/$cpanel_username" ]; then log "/home/$cpanel_username does not exist. Creating directory." mkdir -p "/home/$cpanel_username" log "Successfully created /home/$cpanel_username." else log "/home/$cpanel_username exists." fi # Optionally comment out the listing of homedir contents # log "Contents of $real_backup_files_path/homedir before moving:" # ls -alh "$real_backup_files_path/homedir" | while IFS= read -r line; do # log "$line" # done log "Attempting to sync $real_backup_files_path/homedir to /home/$cpanel_username/" rsync -a "$real_backup_files_path/homedir/" "/home/$cpanel_username/" if [ $? -eq 0 ]; then log "Successfully synced homedir to /home/$cpanel_username." else log "ERROR: Failed to sync homedir to /home/$cpanel_username." fi # Moving main domain files log "Moving main domain files from public_html to $main_domain directory using rsync." if [ ! -d "/home/$cpanel_username/$main_domain" ]; then mkdir -p "/home/$cpanel_username/$main_domain" fi rsync -a "/home/$cpanel_username/public_html/" "/home/$cpanel_username/$main_domain/" if [ $? -eq 0 ]; then log "Successfully synced public_html to $main_domain." else log "ERROR: Failed to sync public_html to $main_domain." fi # Remove public_html if needed rm -rf "/home/$cpanel_username/public_html" } # PERMISSIONS fix_perms(){ log "Changing permissions for all files and folders in user home directory /home/$cpanel_username/" docker exec $cpanel_username bash -c "chown -R 1000:33 /home/$cpanel_username" } # WORDPRESS SITES restore_wordpress() { local real_backup_files_path="$1" local username="$2" if [ "$DRY_RUN" = true ]; then log "DRY RUN: Would restore WordPress sites for user $username" return fi log "Restoring WordPress sites for user $username" if [ -d "$real_backup_files_path/wptoolkit" ]; then for wp_file in "$real_backup_files_path/wptoolkit"/*.json; do log "Importing WordPress site from: $wp_file" opencli wp-import "$username" "$wp_file" done else log "No WordPress data found to restore" fi } # DOMAINS create_domain() { local domain="$1" local type="$2" local username="$3" log "DEBUG: domain='$domain', type='$type', username='$username'" current_domain_count=$((current_domain_count + 1)) log "Restoring $type $domain (${current_domain_count}/${domains_total_count})" # Log the command being executed log "Running command: opencli domains-add '$domain' '$username'" if [ "$DRY_RUN" = true ]; then log "DRY RUN: Would restore $type $domain" else # Check if the domain already exists domain_check_output=$(opencli domains-whoowns "$domain" 2>&1) if echo "$domain_check_output" | grep -q "not found in the database"; then # Proceed to add the domain output=$(opencli domains-add "$domain" "$username" 2>&1) log "opencli domains-add output: $output" if echo "$output" | grep -q "Successfully added domain"; then log "Domain $domain added successfully." else log "ERROR: Failed to add domain $domain. Command output: $output" fi else log "WARNING: $type $domain already exists and will not be added to this user." fi fi } restore_domains() { log "Starting domain restoration process..." if [ -f "$real_backup_files_path/userdata/main" ]; then file_path="$real_backup_files_path/userdata/main" # Log the content of the userdata/main file for debugging log "Contents of userdata/main:" while IFS= read -r line; do log "$line" done < "$file_path" # Initialize variables main_domain="" parked_domains="" sub_domains="" addon_domains="" # Read the file line by line while IFS= read -r line; do if [[ "$line" =~ ^main_domain: ]]; then main_domain=$(echo "$line" | awk '{print $2}') elif [[ "$line" =~ ^parked_domains: ]]; then parked_domains=$(echo "$line" | awk '{print $2}' | tr -d '[]') elif [[ "$line" =~ ^sub_domains: ]]; then sub_domains_section=true continue elif [[ "$line" =~ ^addon_domains: ]]; then addon_domains_section=true continue fi if [[ "$sub_domains_section" == true ]]; then if [[ "$line" =~ ^[[:space:]]+- ]]; then sub_domains+=$(echo "$line" | awk '{print $2}')$'\n' else sub_domains_section=false fi fi if [[ "$addon_domains_section" == true ]]; then if [[ "$line" =~ ^[[:space:]]*[^:]+:[[:space:]]*[^[:space:]]+$ ]]; then domain=$(echo "$line" | awk -F: '{print $1}' | tr -d '[:space:]') # Avoid adding invalid entries and trailing colons if [[ -n "$domain" && "$domain" != "main_domain" && "$domain" != "parked_domains" ]]; then addon_domains+="$domain"$'\n' fi else addon_domains_section=false fi fi done < "$file_path" # Parse parked_domains if [[ -z "$parked_domains" ]]; then parked_domains_array=() else IFS=',' read -r -a parked_domains_array <<< "$parked_domains" fi sub_domains_array=() addon_domains_array=() # Parse sub_domains while IFS= read -r domain; do if [[ -n "$domain" ]]; then sub_domains_array+=("$domain") fi done <<< "$sub_domains" # Parse addon_domains while IFS= read -r domain; do if [[ -n "$domain" ]]; then addon_domains_array+=("$domain") fi done <<< "$addon_domains" # Filter out subdomains that are essentially addon_domain.$main_domain filtered_sub_domains=() for sub_domain in "${sub_domains_array[@]}"; do trimmed_sub_domain=$(echo "$sub_domain" | xargs) is_addon=false for addon in "${addon_domains_array[@]}"; do if [[ "$trimmed_sub_domain" == "$addon.$main_domain" ]]; then is_addon=true break fi done if [ "$is_addon" = false ]; then filtered_sub_domains+=("$trimmed_sub_domain") fi done main_domain_count=1 addon_domains_count=${#addon_domains_array[@]} if [ "${#addon_domains_array[@]}" -eq 1 ] && [ -z "${addon_domains_array[0]}" ]; then addon_domains_count=0 log "No addon domains detected." else log "Addon domains ($addon_domains_count): ${addon_domains_array[@]}" fi parked_domains_count=${#parked_domains_array[@]} if [ "${#parked_domains_array[@]}" -eq 1 ] && [ -z "${parked_domains_array[0]}" ]; then parked_domains_count=0 log "No parked domains detected." else log "Parked domains ($parked_domains_count): ${parked_domains_array[@]}" fi filtered_sub_domains_count=${#filtered_sub_domains[@]} if [ "${#filtered_sub_domains[@]}" -eq 1 ] && [ -z "${filtered_sub_domains[0]}" ]; then filtered_sub_domains_count=0 log "No subdomains detected." else log "Subdomains ($filtered_sub_domains_count): ${filtered_sub_domains[@]}" fi domains_total_count=$((main_domain_count + addon_domains_count + parked_domains_count + filtered_sub_domains_count)) log "Detected a total of $domains_total_count domains for user." current_domain_count=0 # Process the domains log "Processing main (primary) domain..." create_domain "$main_domain" "main domain" "$cpanel_username" if [ "$parked_domains_count" -eq 0 ]; then log "No parked (alias) domains detected." else log "Processing parked (alias) domains..." for parked in "${parked_domains_array[@]}"; do create_domain "$parked" "alias domain" "$cpanel_username" done fi if [ "$addon_domains_count" -eq 0 ]; then log "No addon domains detected." else log "Processing addon domains..." for addon in "${addon_domains_array[@]}"; do create_domain "$addon" "addon domain" "$cpanel_username" done fi if [ "$filtered_sub_domains_count" -eq 0 ]; then log "No subdomains detected." else log "Processing subdomains..." for filtered_sub in "${filtered_sub_domains[@]}"; do create_domain "$filtered_sub" "subdomain" "$cpanel_username" # TODO: create record in DNS zone instead of separate domain if only DNS zone and no folder! done fi log "Finished importing $domains_total_count domains." else log "FATAL ERROR: domains file userdata/main is missing in backup file." exit 1 fi } # CRONJOB restore_cron() { log "Restoring cron jobs for user $cpanel_username" if [ "$DRY_RUN" = true ]; then log "DRY RUN: Would restore cron jobs for user $cpanel_username" return fi if [ -f "$real_backup_files_path/cron/$cpanel_username" ]; then # exclude shell and email variables from file! sed -i '1,2d' "$real_backup_files_path/cron/$cpanel_username" output=$(docker cp $real_backup_files_path/cron/$cpanel_username $cpanel_username:/var/spool/cron/crontabs/$cpanel_username 2>&1) while IFS= read -r line; do log "$line" done <<< "$output" output=$(docker exec $cpanel_username bash -c "crontab -u $cpanel_username /var/spool/cron/crontabs/$cpanel_username" 2>&1) while IFS= read -r line; do log "$line" done <<< "$output" output=$(docker exec $cpanel_username bash -c "service cron restart" 2>&1) while IFS= read -r line; do log "$line" done <<< "$output" docker exec "$cpanel_username" sed -i 's/CRON_STATUS="off"/CRON_STATUS="on"/' /etc/entrypoint.sh >/dev/null 2>&1 else log "No cron jobs found to restore" fi } # Main execution # Main execution main() { local plan_name="$1" # ... previous steps ... parse_cpanel_metadata # Now that we have cpanel_username and cpanel_email, create the user create_new_user "$cpanel_username" "random" "$cpanel_email" "$plan_name" # Restore domains (this creates domain directories) restore_domains # Restore files (now that domain directories exist) restore_files # Adjust permissions fix_perms # Restore PHP version restore_php_version "$php_version" # Restore MySQL databases and users restore_mysql "$mysqldir" # ... remaining steps ... # Cleanup cleanup end_time=$(date +%s) elapsed=$(( end_time - start_time )) hours=$(( elapsed / 3600 )) minutes=$(( (elapsed % 3600 ) / 60 )) seconds=$(( elapsed % 60 )) log "Elapsed time: ${hours}h ${minutes}m ${seconds}s" log "SUCCESS: Import for user $cpanel_username completed successfully." # Run post-hook if provided if [ -n "$post_hook" ]; then if [ -x "$post_hook" ]; then log "Executing post-hook script..." "$post_hook" "$cpanel_username" else log "WARNING: Post-hook file '$post_hook' is not executable or not found." exit 1 fi fi } # MAIN FUNCTION define_data_and_log "$@"