#!/bin/bash # ---------------------------------------------------- # Command: captaincore-do # Description: A collection of useful command-line utilities for managing WordPress sites. # Author: Austin Ginder # License: MIT # ---------------------------------------------------- # --- Global Variables --- CAPTAINCORE_DO_VERSION="1.1" GUM_VERSION="0.14.4" CWEBP_VERSION="1.5.0" RCLONE_VERSION="1.69.3" GUM_CMD="" CWEBP_CMD="" # --- Helper Functions --- # ---------------------------------------------------- # Checks for and installs 'gum' if not present. Sets GUM_CMD on success. # ---------------------------------------------------- function setup_gum() { # Return if already found if [[ -n "$GUM_CMD" ]]; then return 0; fi # If gum is already in the PATH, we're good to go. if command -v gum &> /dev/null; then GUM_CMD="gum" return 0 fi local gum_dir="gum_${GUM_VERSION}_Linux_x86_64" local gum_executable="$HOME/private/${gum_dir}/gum" # Check for a local installation if [ -f "$gum_executable" ] && "$gum_executable" --version &> /dev/null; then GUM_CMD="$gum_executable" return 0 fi # If not found or not working, download it echo "Required tool 'gum' not found. Installing to ~/private..." >&2 mkdir -p "$HOME/private" cd "$HOME/private" || { echo "Error: Could not enter ~/private." >&2; return 1; } local gum_tarball="${gum_dir}.tar.gz" if ! wget --quiet "https://github.com/charmbracelet/gum/releases/download/v${GUM_VERSION}/${gum_tarball}" || ! tar -xf "${gum_tarball}"; then echo "Error: Failed to download or extract gum." >&2 cd - > /dev/null return 1 fi rm -f "${gum_tarball}" # Final check if [ -f "$gum_executable" ] && "$gum_executable" --version &> /dev/null; then echo "'gum' installed successfully." >&2 GUM_CMD="$gum_executable" else echo "Error: gum installation failed." >&2 cd - > /dev/null return 1 fi cd - > /dev/null } # ---------------------------------------------------- # Checks for and installs 'cwebp' if not present. Sets CWEBP_CMD on success. # ---------------------------------------------------- function setup_cwebp() { # Return if already found if [[ -n "$CWEBP_CMD" ]]; then return 0; fi # If cwebp is already in the PATH, we're good to go. if command -v cwebp &> /dev/null; then CWEBP_CMD="cwebp" return 0 fi local cwebp_dir="libwebp-${CWEBP_VERSION}-linux-x86-64" local cwebp_executable="$HOME/private/${cwebp_dir}/bin/cwebp" # Check for a local installation if [ -f "$cwebp_executable" ] && "$cwebp_executable" -version &> /dev/null; then CWEBP_CMD="$cwebp_executable" return 0 fi echo "Required tool 'cwebp' not found. Installing to ~/private..." >&2 mkdir -p "$HOME/private" cd "$HOME/private" || { echo "Error: Could not enter ~/private." >&2; return 1; } local cwebp_tarball="${cwebp_dir}.tar.gz" if ! wget --quiet "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/${cwebp_tarball}" || ! tar -xzf "${cwebp_tarball}"; then echo "Error: Failed to download or extract cwebp." >&2 cd - > /dev/null return 1 fi rm -f "${cwebp_tarball}" # Final check if [ -f "$cwebp_executable" ] && "$cwebp_executable" -version &> /dev/null; then echo "'cwebp' installed successfully." >&2 CWEBP_CMD="$cwebp_executable" else echo "Error: cwebp installation failed." >&2 cd - > /dev/null return 1 fi cd - > /dev/null } # ---------------------------------------------------- # Checks for and installs 'rclone' if not present. # Sets RCLONE_CMD on success. # ---------------------------------------------------- function setup_rclone() { # Return if already found if [[ -n "$RCLONE_CMD" ]]; then return 0; fi # If rclone is already in the PATH, we're good to go. if command -v rclone &> /dev/null; then RCLONE_CMD="rclone" return 0 fi local rclone_dir="rclone-v${RCLONE_VERSION}-linux-amd64" local rclone_executable="$HOME/private/${rclone_dir}/rclone" # Check for a local installation if [ -f "$rclone_executable" ] && "$rclone_executable" --version &> /dev/null; then RCLONE_CMD="$rclone_executable" return 0 fi # If not found or not working, download it echo "Required tool 'rclone' not found. Installing to ~/private..." >&2 if ! command -v unzip &>/dev/null; then echo "Error: 'unzip' command is required for installation." >&2; return 1; fi mkdir -p "$HOME/private" cd "$HOME/private" || { echo "Error: Could not enter ~/private." >&2; return 1; } local rclone_zip="rclone-v${RCLONE_VERSION}-linux-amd64.zip" if ! wget --quiet "https://github.com/rclone/rclone/releases/download/v${RCLONE_VERSION}/${rclone_zip}" || ! unzip -q "${rclone_zip}"; then echo "Error: Failed to download or extract rclone." >&2 cd - > /dev/null return 1 fi rm -f "${rclone_zip}" # Final check if [ -f "$rclone_executable" ] && "$rclone_executable" --version &> /dev/null; then echo "'rclone' installed successfully." >&2 RCLONE_CMD="$rclone_executable" else echo "Error: rclone installation failed." >&2 cd - > /dev/null return 1 fi cd - > /dev/null } # ---------------------------------------------------- # Displays detailed help for a specific command. # ---------------------------------------------------- function show_command_help() { local cmd="$1" # If no command is specified, show the general usage. if [ -z "$cmd" ]; then show_usage return fi # Display help text based on the command provided. case "$cmd" in backup) echo "Creates a full backup (files + DB) of a WordPress site." echo echo "Usage: captaincore-do backup " ;; backup-db) echo "Performs a DB-only backup to a secure private directory." echo echo "Usage: captaincore-do backup-db" ;; clean) echo "Cleans up unused WordPress components or analyzes disk usage." echo echo "Usage: captaincore-do clean " echo echo "Subcommands:" echo " themes Deletes all inactive themes except for the latest default WordPress theme." echo " disk Provides an interactive disk usage analysis for the current directory." ;; db-check-autoload) echo "Checks the size and top 25 largest autoloaded options in the DB." echo echo "Usage: captaincore-do db-check-autoload" ;; db-optimize) echo "Converts all MyISAM database tables to the InnoDB engine." echo echo "Usage: captaincore-do db-optimize" ;; dump) echo "Dumps the content of files matching a pattern into a single text file." echo echo "Usage: captaincore-do dump \"\" [-x ] [-x ]..." echo echo "Arguments:" echo " (Required) The path and file pattern to search for, enclosed in quotes." echo echo "Flags:" echo " -x (Optional) A file or directory pattern to exclude. Can be used multiple times." echo " To exclude a directory, the pattern MUST end with a forward slash (e.g., 'my-dir/')." echo echo "Examples:" echo " captaincore-do dump \"wp-content/plugins/my-plugin/**/*.php\"" echo " captaincore-do dump \"*\" -x \"*.log\" -x \"node_modules/\"" ;; convert-to-webp) echo "Finds and converts large images (JPG, PNG) to WebP format." echo echo "Usage: captaincore-do convert-to-webp" ;; migrate) echo "Migrates a site from a backup snapshot." echo echo "Usage: captaincore-do migrate --url= [--update-urls]" echo echo " --update-urls Update urls to destination WordPress site. Default will keep source urls." ;; monitor) echo "Monitors server access logs in real-time." echo echo "Usage: captaincore-do monitor [--top=] [--now]" echo echo "Optional Flags:" echo " --top= The number of top IP/Status combinations to show. Default is 25." echo " --now Start processing from the end of the log file instead of the beginning." ;; reset) echo "Resets the WordPress installation to a default state." echo echo "Usage: captaincore-do reset --admin_user= [--admin_email=]" echo echo "Flags:" echo " --admin_user= (Required) The username for the new administrator." echo " --admin_email= (Optional) The email for the new administrator." echo " Defaults to the current site's admin email." ;; reset-permissions) echo "Resets file and folder permissions to defaults (755 for dirs, 644 for files)." echo echo "Usage: captaincore-do reset-permissions" ;; slow-plugins) echo "Identifies plugins that may be slowing down WP-CLI." echo echo "Usage: captaincore-do slow-plugins" ;; suspend) echo "Activates or deactivates a suspend message shown to visitors." echo echo "Usage: captaincore-do suspend [flags]" echo echo "Subcommands:" echo " activate Activates the suspend message. Requires --name and --link flags." echo " deactivate Deactivates the suspend message." echo echo "Flags for 'activate':" echo " --name= (Required) The name of the business to display." echo " --link= (Required) The contact link for the business." echo " --wp-content= (Optional) Path to wp-content directory. Defaults to 'wp-content'." echo echo "Flags for 'deactivate':" echo " --wp-content= (Optional) Path to wp-content directory. Defaults to 'wp-content'." ;; version) echo "Displays the current version of the captaincore-do script." echo echo "Usage: captaincore-do version" ;; *) echo "Error: Unknown command '$cmd' for help." >&2 echo "" show_usage exit 1 ;; esac } # ---------------------------------------------------- # Displays the main help and usage information. # ---------------------------------------------------- function show_usage() { echo "CaptainCore Do (v$CAPTAINCORE_DO_VERSION)" echo "--------------------------" echo "A collection of useful command-line utilities for managing WordPress sites." echo "" echo "Usage:" echo " captaincore-do [arguments] [--flags]" echo "" echo "Available Commands:" echo " backup Creates a full backup (files + DB) of a WordPress site." echo " backup-db Performs a DB-only backup to a secure private directory." echo " clean Removes unused items like inactive themes." echo " convert-to-webp Finds and converts large images (JPG, PNG) to WebP format." echo " db-check-autoload Checks the size of autoloaded options in the database." echo " db-optimize Converts all MyISAM database tables to the InnoDB engine." echo " dump Dumps the content of files matching a pattern into a single text file." echo " migrate Migrates a site from a backup URL or local file." echo " monitor Monitors server access logs in real-time with top IP/status hits." echo " reset Resets the WordPress installation to a default state." echo " reset-permissions Resets file and folder permissions to defaults." echo " slow-plugins Identifies plugins that may be slowing down WP-CLI." echo " suspend Activates or deactivates a suspend message shown to visitors." echo " version Displays the current version of the captaincore-do script." echo "" echo "Run 'captaincore-do help ' for more information on a specific command." } # --- Main Entry Point and Argument Parser --- function main() { # If no arguments are provided, show usage and exit. if [ $# -eq 0 ]; then show_usage exit 0 fi # --- Help Flag Handling --- # Detect 'help ' pattern if [[ "$1" == "help" ]]; then show_command_help "$2" exit 0 fi # Detect ' --help' pattern for arg in "$@"; do if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then # The first non-flag argument is the command we need help for. local help_for_cmd="" for inner_arg in "$@"; do # Find the first argument that doesn't start with a hyphen. if [[ ! "$inner_arg" =~ ^- ]]; then help_for_cmd="$inner_arg" break fi done show_command_help "$help_for_cmd" exit 0 fi done # --- Centralized Argument Parser --- # This loop separates flags from commands. local url_flag="" local top_flag="" local name_flag="" local link_flag="" local wp_content_flag="" local update_urls_flag="" local now_flag="" local admin_user_flag="" local admin_email_flag="" local exclude_patterns=() # Array for exclude patterns local positional_args=() # To store commands and their direct arguments while [[ $# -gt 0 ]]; do case $1 in --url=*) url_flag="${1#*=}" shift ;; --top=*) top_flag="${1#*=}" shift ;; --now) now_flag=true shift ;; --name=*) name_flag="${1#*=}" shift ;; --link=*) link_flag="${1#*=}" shift ;; --wp-content=*) wp_content_flag="${1#*=}" shift ;; --update-urls) update_urls_flag=true shift ;; --admin_user=*) admin_user_flag="${1#*=}" shift ;; --admin_email=*) admin_email_flag="${1#*=}" shift ;; -x) # Exclude flag if [[ -n "$2" ]]; then exclude_patterns+=("$2") shift 2 # past flag and value else echo "Error: -x flag requires an argument." >&2 exit 1 fi ;; -*) # This will catch unknown flags like --foo echo "Error: Unknown flag: $1" >&2 show_usage exit 1 ;; *) # It's a command or a positional argument positional_args+=("$1") shift # past argument ;; esac done # The first positional argument is the main command. local command="${positional_args[0]}" # --- Command Router --- # This routes to the correct function based on the parsed command. case "$command" in backup) full_backup "${positional_args[1]}" ;; backup-db) db_backup ;; clean) local arg1="${positional_args[1]}" case "$arg1" in themes) clean_themes ;; disk) clean_disk ;; *) show_command_help "clean" exit 0 ;; esac ;; db-check-autoload) db_check_autoload ;; db-optimize) db_optimize ;; convert-to-webp) convert_to_webp ;; dump) # There should be exactly 2 positional args total: 'dump' and the pattern. if [ ${#positional_args[@]} -ne 2 ]; then echo -e "Error: Incorrect number of arguments for 'dump'. It's likely your pattern was expanded by the shell." >&2 echo "Please wrap the input pattern in double quotes." >&2 echo -e "\n Usage: captaincore-do dump \"\" [-x ...]" >&2 return 1 fi run_dump "${positional_args[1]}" "${exclude_patterns[@]}" ;; migrate) if [[ -z "$url_flag" ]]; then echo "Error: The 'migrate' command requires the --url=<...> flag." >&2 show_command_help "migrate" exit 1 fi migrate_site "$url_flag" "$update_urls_flag" ;; monitor) monitor_traffic "$top_flag" "$now_flag" ;; reset) if [[ -z "$admin_user_flag" ]]; then echo "Error: The 'reset' command requires the --admin_user=<...> flag." >&2 show_command_help "reset" exit 1 fi reset_site "$admin_user_flag" "$admin_email_flag" ;; reset-permissions) reset_permissions ;; slow-plugins) identify_slow_plugins ;; suspend) local arg1="${positional_args[1]}" case "$arg1" in activate) suspend_activate "$name_flag" "$link_flag" "$wp_content_flag" ;; deactivate) suspend_deactivate "$wp_content_flag" ;; *) show_command_help "suspend" exit 0 ;; esac ;; version|--version|-v) show_version ;; *) echo "Error: Unknown command '$command'." >&2 show_usage exit 1 ;; esac } # Pass all script arguments to the main function. # --- Sourced Command Functions --- # The following functions are sourced from the 'commands/' directory. # ---------------------------------------------------- # Creates a full backup of a WordPress site (files + database). # ---------------------------------------------------- function full_backup() { local target_folder="$1" if [ -z "$target_folder" ]; then echo "Error: Please provide a folder path." >&2; echo "Usage: captaincore-do backup " >&2; return 1; fi if ! command -v realpath &> /dev/null; then echo "Error: 'realpath' command not found. Please install it." >&2; return 1; fi if [ ! -d "$target_folder" ]; then echo "Error: Folder '$target_folder' not found." >&2; return 1; fi # Resolve the absolute path to handle cases like "." local full_target_path; full_target_path=$(realpath "$target_folder") local parent_dir; parent_dir=$(dirname "$full_target_path") local site_dir_name; site_dir_name=$(basename "$full_target_path") local today; today=$(date +"%Y-%m-%d"); local random; random=$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 7); local backup_filename="${today}_${random}.zip"; local original_dir; original_dir=$(pwd) # Change to the parent directory for consistent relative paths in the zip cd "$parent_dir" || return 1 if ! command -v wp &> /dev/null; then echo "Error: wp-cli is not installed." >&2; cd "$original_dir"; return 1; fi local home_url; home_url=$(wp option get home --path="$site_dir_name" --skip-plugins --skip-themes); local name; name=$(wp option get blogname --path="$site_dir_name" --skip-plugins --skip-themes); local database_file="db_export.sql" echo "Exporting database for '$name'..."; if ! wp db export "$site_dir_name/$database_file" --path="$site_dir_name" --add-drop-table --default-character-set=utf8mb4; then echo "Error: Database export failed." >&2 cd "$original_dir" return 1 fi echo "Creating zip archive..."; # Create the zip in the parent directory, zipping the site directory if ! zip -r "$backup_filename" "$site_dir_name" -x "$site_dir_name/wp-content/updraft/*" > /dev/null; then echo "Error: Failed to zip files." >&2 rm -f "$site_dir_name/$database_file" cd "$original_dir" return 1 fi # Add wp-config.php if it exists in the site directory if [ -f "$site_dir_name/wp-config.php" ]; then zip "$backup_filename" "$site_dir_name/wp-config.php" > /dev/null fi # Cleanup and Final Steps local size; size=$(ls -lh "$backup_filename" | awk '{print $5}') rm -f "$site_dir_name/$database_file" mv "$backup_filename" "$site_dir_name/" local final_backup_location="$site_dir_name/$backup_filename" cd "$original_dir" echo "-----------------------------------------------------"; echo "✅ Full site backup complete!"; echo " Name: $name"; echo " Location: $final_backup_location"; echo " Size: $size"; echo " URL: ${home_url}/${backup_filename}"; echo "-----------------------------------------------------"; echo "When done, remember to remove the backup file."; echo "rm -f \"$full_target_path/$backup_filename\"" } # ---------------------------------------------------- # Performs a WordPress database-only backup to a secure, private directory. # ---------------------------------------------------- function db_backup() { echo "Starting database-only backup..." local home_directory; home_directory=$(pwd); local private_directory="" if [ -d "_wpeprivate" ]; then private_directory="${home_directory}/_wpeprivate"; elif [ -d "../private" ]; then private_directory=$(cd ../private && pwd); elif [ -d "../tmp" ]; then private_directory=$(cd ../tmp && pwd); fi cd "$home_directory" if [[ -z "$private_directory" ]]; then echo "Error: Can't find a private directory." >&2; return 1; fi if ! command -v wp &> /dev/null; then echo "Error: wp-cli is not installed." >&2; return 1; fi local database_name; database_name=$(wp config get DB_NAME --skip-plugins --skip-themes --quiet); local database_username; database_username=$(wp config get DB_USER --skip-plugins --skip-themes --quiet); local database_password; database_password=$(wp config get DB_PASSWORD --skip-plugins --skip-themes --quiet); local dump_command; if command -v mariadb-dump &> /dev/null; then dump_command="mariadb-dump"; elif command -v mysqldump &> /dev/null; then dump_command="mysqldump"; else echo "Error: Neither mariadb-dump nor mysqldump could be found." >&2; return 1; fi echo "Using ${dump_command} for the backup." local backup_file="${private_directory}/database-backup-$(date +"%Y-%m-%d").sql" if ! "${dump_command}" -u"${database_username}" -p"${database_password}" --max_allowed_packet=512M --default-character-set=utf8mb4 --add-drop-table --single-transaction --quick --lock-tables=false "${database_name}" > "${backup_file}"; then echo "Error: Database dump failed." >&2; rm -f "${backup_file}"; return 1; fi chmod 600 "${backup_file}"; echo "✅ Database backup complete!"; echo " Backup file located at: ${backup_file}" } # ---------------------------------------------------- # Cleans up inactive themes. # ---------------------------------------------------- function clean_themes() { # --- Pre-flight checks --- if ! command -v wp &>/dev/null; then echo "❌ Error: WP-CLI not found." >&2; return 1; fi if ! wp core is-installed --quiet; then echo "❌ Error: This does not appear to be a WordPress installation." >&2; return 1; fi echo "🔎 Finding the latest default WordPress theme to preserve..." latest_default_theme=$(cat <<'PHP_SCRIPT' | wp eval-file - themes) === false) { exit(1); } $twentyThemes = []; foreach ($data->themes as $theme) { if (isset($theme->slug) && str_starts_with($theme->slug, 'twenty')) { $twentyThemes[] = $theme->slug; } } if (empty($twentyThemes)) { exit(1); } echo $twentyThemes[0]; PHP_SCRIPT ) if [ $? -ne 0 ] || [ -z "$latest_default_theme" ]; then echo "❌ Error: Could not determine the latest default theme. Aborting." >&2 return 1 fi echo "✅ The latest default theme is '$latest_default_theme'. This will be preserved." inactive_themes=($(wp theme list --status=inactive --field=name --skip-plugins --skip-themes)) if [ ${#inactive_themes[@]} -eq 0 ]; then echo "👍 No inactive themes found to process. All done!" return 0 fi echo "🚀 Processing ${#inactive_themes[@]} inactive themes..." for theme in "${inactive_themes[@]}"; do # Check if the current inactive theme is the one we want to keep if [[ "$theme" == "$latest_default_theme" ]]; then echo "âšĒī¸ Keeping inactive default theme: $theme" else echo "❌ Deleting inactive theme: $theme" wp theme delete "$theme" fi done echo "✨ Cleanup complete." } # ---------------------------------------------------- # Analyzes disk usage using rclone. # ---------------------------------------------------- function clean_disk() { echo "🚀 Launching interactive disk usage analysis..." if ! setup_rclone; then echo "Aborting analysis: rclone setup failed." >&2 return 1 fi "$RCLONE_CMD" ncdu . } # ---------------------------------------------------- # Finds large images and converts them to the WebP format. # ---------------------------------------------------- function convert_to_webp() { echo "🚀 Starting WebP Conversion Process 🚀" if ! setup_cwebp; then echo "Aborting conversion: cwebp setup failed." >&2 return 1 fi if ! command -v identify &> /dev/null; then echo "❌ Error: 'identify' command not found. Please install ImageMagick." >&2; return 1; fi local uploads_dir="wp-content/uploads"; if [ ! -d "$uploads_dir" ]; then echo "❌ Error: Cannot find '$uploads_dir' directory." >&2; return 1; fi local before_size; before_size="$(du -sh "$uploads_dir" | awk '{print $1}')"; echo "Current uploads size: $before_size" local files; files=$(find "$uploads_dir" -type f -size +1M \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" \)) if [[ -z "$files" ]]; then echo "✅ No images larger than 1MB found to convert."; return 0; fi local count; count=$(echo "$files" | wc -l); echo "Found $count image(s) over 1MB to process..."; echo "" echo "$files" | while IFS= read -r file; do if [[ "$(identify -format "%m" "$file")" == "WEBP" ]]; then echo "Skipping (already WebP): $file"; continue; fi local temp_file="${file}.temp.webp"; local before_file_size; before_file_size=$(du -sh --apparent-size "$file" | awk '{print $1}') "$CWEBP_CMD" -q 80 "$file" -o "$temp_file" > /dev/null 2>&1 if [ -s "$temp_file" ]; then mv "$temp_file" "$file"; local after_file_size; after_file_size=$(du -sh --apparent-size "$file" | awk '{print $1}') echo "✅ Converted ($before_file_size -> $after_file_size): $file" else rm -f "$temp_file"; echo "❌ Failed conversion: $file" fi done echo ""; local after_size; after_size="$(du -sh "$uploads_dir" | awk '{print $1}')" echo "-----------------------------------------------------"; echo "✅ Bulk conversion complete!"; echo " Uploads folder size reduced from $before_size to $after_size."; echo "-----------------------------------------------------" } # ---------------------------------------------------- # Checks the size and contents of autoloaded options in the WordPress database. # ---------------------------------------------------- function db_check_autoload() { echo "Checking autoloaded options in the database..." if ! command -v wp &> /dev/null; then echo "Error: wp-cli is not installed." >&2; return 1; fi if ! command wp core is-installed --quiet; then echo "Error: This does not appear to be a WordPress installation." >&2; return 1; fi echo echo "--- Total Autoloaded Size ---" wp db query "SELECT ROUND(SUM(LENGTH(option_value))/1024/1024, 2) as 'Autoload MB' FROM $(wp db prefix)options WHERE autoload='yes';" echo echo "--- Top 25 Autoloaded Options & Totals ---" wp db query "SELECT 'autoloaded data in MB' as name, ROUND(SUM(LENGTH(option_value))/ 1024 / 1024, 2) as value FROM $(wp db prefix)options WHERE autoload='yes' UNION SELECT 'autoloaded data count', count(*) FROM $(wp db prefix)options WHERE autoload='yes' UNION (SELECT option_name, round( length(option_value) / 1024 / 1024, 2) FROM $(wp db prefix)options WHERE autoload='yes' ORDER BY length(option_value) DESC LIMIT 25)" echo echo "✅ Autoload check complete." } # ---------------------------------------------------- # Optimizes the database by converting tables to InnoDB, reporting large tables, and cleaning transients. # ---------------------------------------------------- function db_optimize() { # --- Pre-flight checks --- if ! command -v wp &>/dev/null; then echo "Error: WP-CLI not found." >&2; return 1; fi if ! wp core is-installed --quiet; then echo "Error: This does not appear to be a WordPress installation." >&2; return 1; fi echo "🚀 Starting database optimization..." echo "" # --- Step 1: Convert MyISAM to InnoDB --- echo "--- Step 1: Checking for MyISAM tables to convert to InnoDB ---" local myisam_tables myisam_tables=$(wp db query "SELECT TABLE_NAME FROM information_schema.TABLES WHERE ENGINE = 'MyISAM' AND TABLE_SCHEMA = DATABASE()" --skip-column-names) if [[ -z "$myisam_tables" ]]; then echo "✅ All tables are already using the InnoDB engine. No conversion needed." else echo "Found the following MyISAM tables to convert:" echo "$myisam_tables" echo "Converting tables to InnoDB..." wp db query "SELECT CONCAT('ALTER TABLE \`', TABLE_NAME, '\` ENGINE=InnoDB;') FROM information_schema.TABLES WHERE ENGINE = 'MyISAM' AND TABLE_SCHEMA = DATABASE()" --skip-column-names | wp db query if [ $? -eq 0 ]; then echo "✅ Successfully converted tables to InnoDB." else echo "❌ An error occurred during the conversion." return 1 fi fi # --- Step 2: List Top 10 Largest Tables --- echo "" echo "--- Step 2: Top 10 Tables Larger Than 1MB ---" wp db query " SELECT TABLE_NAME, CASE WHEN (data_length + index_length) >= 1073741824 THEN CONCAT(ROUND((data_length + index_length) / 1073741824, 2), ' GB') WHEN (data_length + index_length) >= 1048576 THEN CONCAT(ROUND((data_length + index_length) / 1048576, 2), ' MB') WHEN (data_length + index_length) >= 1024 THEN CONCAT(ROUND((data_length + index_length) / 1024, 2), ' KB') ELSE CONCAT((data_length + index_length), ' B') END AS Size FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND (data_length + index_length) > 1048576 ORDER BY (data_length + index_length) DESC LIMIT 10; " # --- Step 3: Delete Expired Transients --- echo "" echo "--- Step 3: Deleting Expired Transients ---" wp transient delete --expired echo "" echo "✅ Database optimization complete." } # ---------------------------------------------------- # Dumps the content of files matching a pattern into a single text file. # ---------------------------------------------------- function run_dump() { # --- 1. Validate Input --- local INPUT_PATTERN="$1" shift local exclude_patterns=("$@") if [ -z "$INPUT_PATTERN" ]; then echo "Error: No input pattern provided." >&2 echo "Usage: captaincore-do dump \"\" [-x ]..." >&2 return 1 fi # --- 2. Determine Paths and Names --- local SEARCH_DIR SEARCH_DIR=$(dirname "$INPUT_PATTERN") local FILE_PATTERN FILE_PATTERN=$(basename "$INPUT_PATTERN") local OUTPUT_BASENAME OUTPUT_BASENAME=$(basename "$SEARCH_DIR") if [ "$OUTPUT_BASENAME" == "." ]; then OUTPUT_BASENAME="dump" fi local OUTPUT_FILE="${OUTPUT_BASENAME}.txt" # --- 3. Process Files --- > "$OUTPUT_FILE" echo "Searching in '$SEARCH_DIR' for files matching '$FILE_PATTERN'..." if [ ${#exclude_patterns[@]} -gt 0 ]; then echo "Excluding user-defined patterns: ${exclude_patterns[*]}" fi echo "Automatically excluding: .git directory contents" # Dynamically build the find command local find_cmd=("find" "$SEARCH_DIR" "-type" "f" "-name" "$FILE_PATTERN") # Add user-defined exclusions for pattern in "${exclude_patterns[@]}"; do if [[ "$pattern" == */ ]]; then # For directories (pattern ends with /), use -path local dir_pattern=${pattern%/} # remove trailing slash find_cmd+=("-not" "-path" "*/$dir_pattern/*") else # For files, use -name find_cmd+=("-not" "-name" "$pattern") fi done # Add automatic exclusions for the output file and .git directory find_cmd+=("-not" "-name" "$OUTPUT_FILE") find_cmd+=("-not" "-path" "*/.git/*") # Execute the find command "${find_cmd[@]}" -print0 | while IFS= read -r -d '' file; do echo "--- File: $file ---" >> "$OUTPUT_FILE" cat "$file" >> "$OUTPUT_FILE" echo -e "\n" >> "$OUTPUT_FILE" done # --- 4. Final Report --- if [ ! -s "$OUTPUT_FILE" ]; then echo "Warning: No files found matching the pattern. No dump file created." rm "$OUTPUT_FILE" return 0 fi local FILE_SIZE FILE_SIZE=$(du -h "$OUTPUT_FILE" | cut -f1 | xargs) echo "Generated $OUTPUT_FILE ($FILE_SIZE)" } # ---------------------------------------------------- # Migrates a site from a backup URL or local file. # Arguments: # $1 - The URL/path for the backup file. # $2 - A flag indicating whether to update URLs. # ---------------------------------------------------- function migrate_site() { local backup_url="$1" local update_urls_flag="$2" echo "🚀 Starting Site Migration 🚀" # --- Pre-flight Checks --- if ! command -v wp &>/dev/null; then echo "❌ Error: WP-CLI not found." >&2; return 1; fi if ! command -v wget &>/dev/null; then echo "❌ Error: wget not found." >&2; return 1; fi if ! command -v unzip &>/dev/null; then echo "❌ Error: unzip not found." >&2; return 1; fi if ! command -v tar &>/dev/null; then echo "❌ Error: tar not found." >&2; return 1; fi local home_directory; home_directory=$(pwd) local wp_home; wp_home=$( wp option get home --skip-themes --skip-plugins ) if [[ "$wp_home" != "http"* ]]; then echo "❌ Error: WordPress not found in current directory. Migration cancelled." >&2 return 1 fi # --- Find Private Directory --- local private_dir="" if [ -d "_wpeprivate" ]; then private_dir="${home_directory}/_wpeprivate"; fi if [ -d "../private" ]; then private_dir=$(cd ../private && pwd); fi if [ -d "../tmp" ]; then private_dir=$(cd ../tmp && pwd); fi cd "$home_directory" # Ensure we are back if [[ -z "$private_dir" ]]; then echo "❌ Error: Can't locate a private folder. Migration cancelled." >&2 return 1 fi # --- Download and Extract Backup --- local timedate; timedate=$(date +'%Y-%m-%d-%H%M%S') local restore_dir="${private_dir}/restore_${timedate}" mkdir -p "$restore_dir" cd "$restore_dir" || return 1 local local_file_name; local_file_name=$(basename "$backup_url") # Handle special URLs if [[ "$backup_url" == *"admin-ajax.php"* ]]; then echo "â„šī¸ Backup Buddy URL found, transforming..." backup_url=${backup_url/wp-admin\/admin-ajax.php?action=pb_backupbuddy_backupbuddy&function=download_archive&backupbuddy_backup=/wp-content\/uploads\/backupbuddy_backups/} fi if [[ "$backup_url" == *"dropbox.com"* && "$backup_url" != *"dl=1" ]]; then echo "â„šī¸ Dropbox URL found, adding dl=1..." backup_url=${backup_url/&dl=0/&dl=1} fi # Download or use local file if [ ! -f "${private_dir}/${local_file_name}" ]; then echo "Downloading from $backup_url..." wget --no-check-certificate --progress=bar:force:noscroll -O "backup_file" "$backup_url" if [ $? -ne 0 ]; then echo "❌ Error: Download failed."; cd "$home_directory"; return 1; fi else echo "â„šī¸ Local file '${local_file_name}' found. Using it." mv "${private_dir}/${local_file_name}" ./backup_file fi # Extract based on extension echo "Extracting backup..." if [[ "$backup_url" == *".zip"* || "$local_file_name" == *".zip"* ]]; then unzip -q -o backup_file -x "__MACOSX/*" "cgi-bin/*" elif [[ "$backup_url" == *".tar.gz"* || "$local_file_name" == *".tar.gz"* ]]; then tar xzf backup_file elif [[ "$backup_url" == *".tar"* || "$local_file_name" == *".tar"* ]]; then tar xf backup_file else # Assume zip if no extension matches echo "â„šī¸ No clear extension, assuming .zip format." unzip -q -o backup_file -x "__MACOSX/*" "cgi-bin/*" fi rm -f backup_file # --- Migrate Files --- local wordpresspath; wordpresspath=$( find . -type d -name 'wp-content' -print -quit ) if [[ -z "$wordpresspath" ]]; then echo "❌ Error: Can't find wp-content/ in backup. Migration cancelled."; cd "$home_directory"; return 1 fi echo "Migrating files..." # A safer way to migrate content is using rsync rsync -av --remove-source-files "${wordpresspath}/" "${home_directory}/wp-content/" # Find and move non-default root files local backup_root_dir; backup_root_dir=$(dirname "$wordpresspath") cd "$backup_root_dir" || return 1 local default_files=( index.php license.txt readme.html wp-activate.php wp-app.php wp-blog-header.php wp-comments-post.php wp-config-sample.php wp-cron.php wp-links-opml.php wp-load.php wp-login.php wp-mail.php wp-pass.php wp-register.php wp-settings.php wp-signup.php wp-trackback.php xmlrpc.php wp-admin wp-config.php wp-content wp-includes ) for item in *; do is_default=false for default in "${default_files[@]}"; do if [[ "$item" == "$default" ]]; then is_default=true; break; fi done if ! $is_default; then echo "Moving root item: $item" mv -f "$item" "${home_directory}/" fi done cd "$home_directory" # --- Database Migration --- local database; database=$(find "$restore_dir" -type f -name '*.sql' -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" ") if [[ -z "$database" || ! -f "$database" ]]; then echo "âš ī¸ Warning: No .sql file found in backup. Skipping database import."; else echo "Importing database from $database..." local search_privacy; search_privacy=$( wp option get blog_public --skip-plugins --skip-themes ) wp db reset --yes --skip-plugins --skip-themes wp db import "$database" --skip-plugins --skip-themes wp cache flush --skip-plugins --skip-themes wp option update blog_public "$search_privacy" --skip-plugins --skip-themes # URL updates local wp_home_imported; wp_home_imported=$( wp option get home --skip-plugins --skip-themes ) if [[ "$update_urls_flag" == "true" && "$wp_home_imported" != "$wp_home" ]]; then echo "Updating URLs from $wp_home_imported to $wp_home..." wp search-replace "$wp_home_imported" "$wp_home" --all-tables --report-changed-only --skip-plugins --skip-themes fi fi # --- Cleanup & Final Steps --- echo "Performing cleanup and final optimizations..." local plugins_to_remove=( backupbuddy wordfence w3-total-cache wp-super-cache ewww-image-optimizer ) for plugin in "${plugins_to_remove[@]}"; do if wp plugin is-installed "$plugin" --skip-plugins --skip-themes &>/dev/null; then echo "Removing plugin: $plugin" wp plugin delete "$plugin" --skip-plugins --skip-themes fi done # Convert tables to InnoDB local alter_queries; alter_queries=$(wp db query "SELECT CONCAT('ALTER TABLE ', TABLE_SCHEMA,'.', TABLE_NAME, ' ENGINE=InnoDB;') FROM information_schema.TABLES WHERE ENGINE = 'MyISAM' AND TABLE_SCHEMA=DATABASE()" --skip-column-names --skip-plugins --skip-themes) if [[ -n "$alter_queries" ]]; then echo "Converting MyISAM tables to InnoDB..." echo "$alter_queries" | wp db query --skip-plugins --skip-themes fi wp rewrite flush --skip-plugins --skip-themes if wp plugin is-active woocommerce --skip-plugins --skip-themes &>/dev/null; then wp wc tool run regenerate_product_attributes_lookup_table --user=1 --skip-plugins --skip-themes fi find . -type d -exec chmod 755 {} \; find . -type f -exec chmod 644 {} \; # Clean up restore directory rm -rf "$restore_dir" echo "✅ Site migration complete!" } # ---------------------------------------------------- # Monitors server access logs in real-time. # ---------------------------------------------------- function monitor_traffic() { local limit_arg="$1" local process_from_now="$2" if ! setup_gum; then echo "Aborting monitor: gum setup failed." >&2 return 1 fi # --- Configuration --- local limit=${limit_arg:-25} local log="$HOME/logs/access.log" local initial_lines_to_process=500 # How many lines to look back initially (only used with --now) # --- End Configuration --- if [ ! -f "$log" ]; then echo "Error: Log file not found at $log" >&2 exit 1 fi # --- Initial Setup --- local start_line=1 # Default: start from the beginning if [ "$process_from_now" = true ]; then echo "Processing from near the end (--now specified)." >&2 local initial_log_count; initial_log_count=$(wc -l < "$log") local calculated_start=$((initial_log_count - initial_lines_to_process + 1)) if [ $calculated_start -gt 1 ]; then start_line=$calculated_start else start_line=1 fi else echo "Processing from the beginning of the log (line 1)." >&2 fi echo "Starting analysis from line: $start_line | Top hits limit: $limit" >&2 # --- End Initial Setup --- trap "echo; echo 'Monitoring stopped.'; exit 0" INT sleep 2 # Give user time to read initial messages while true; do local current_log_count; current_log_count=$(wc -l < "$log") if [ "$current_log_count" -lt "$start_line" ]; then echo "Warning: Log file appears to have shrunk or rotated. Resetting start line to 1." >&2 start_line=1 sleep 1 current_log_count=$(wc -l < "$log") if [ "$current_log_count" -lt 1 ]; then echo "Log file is empty or unreadable after reset. Waiting..." >&2 sleep 5 continue fi fi local actual_lines_processed=$((current_log_count - start_line + 1)) if [ $actual_lines_processed -lt 0 ]; then actual_lines_processed=0 fi local overview_header="PHP Workers,Log File,Processed,From Time,To Time\n" local overview_data="" local php_workers; php_workers=$(ps -eo pid,uname,comm,%cpu,%mem,time --sort=time --no-headers | grep '[p]hp-fpm' | grep -v 'root' | wc -l) local first_line_time; first_line_time=$(sed -n "${start_line}p" "$log" | awk -F'[][]' '{print $2}' | head -n 1) [ -z "$first_line_time" ] && first_line_time="N/A" local last_line_time; last_line_time=$(tail -n 1 "$log" | awk -F'[][]' '{print $2}' | head -n 1) [ -z "$last_line_time" ] && last_line_time="N/A" overview_data+="$php_workers,$log,$actual_lines_processed,$first_line_time,$last_line_time\n" local output_header="Hits,IP Address,Status Code,Last User Agent\n" local output_data="" local top_combinations; top_combinations=$(timeout 10s sed -n "$start_line,\$p" "$log" | \ awk '{print $2 " " $8}' | \ sort | \ uniq -c | \ sort -nr | \ head -n "$limit") if [ -z "$top_combinations" ]; then output_data+="0,No new data,-,\"N/A\"\n" else while IFS= read -r line; do line=$(echo "$line" | sed 's/^[ \t]*//;s/[ \t]*$//') local count ip status_code read -r count ip status_code <<< "$line" if ! [[ "$count" =~ ^[0-9]+$ ]] || [[ -z "$ip" ]] || ! [[ "$status_code" =~ ^[0-9]+$ ]]; then continue fi local ip_user_agent; ip_user_agent=$(timeout 2s sed -n "$start_line,\$p" "$log" | grep " $ip " | tail -n 1 | awk -F\" '{print $6}' | cut -c 1-100) ip_user_agent=${ip_user_agent//,/} ip_user_agent=${ip_user_agent//\"/} [ -z "$ip_user_agent" ] && ip_user_agent="-" output_data+="$count,$ip,$status_code,\"$ip_user_agent\"\n" done < <(echo -e "$top_combinations") fi clear echo "--- Overview (Lines $start_line - $current_log_count | Total: $actual_lines_processed) ---" echo -e "$overview_header$overview_data" | "$GUM_CMD" table --print echo echo "--- Top $limit IP/Status Hits (Lines $start_line - $current_log_count) ---" echo -e "$output_header$output_data" | "$GUM_CMD" table --print sleep 2 done } # --- File: ./commands/reset --- # ---------------------------------------------------- # Resets the WordPress installation to a clean, default state. # ---------------------------------------------------- function reset_site() { local admin_user="$1" local admin_email="$2" # --- Pre-flight Checks --- if ! command -v wp &>/dev/null; then echo "❌ Error: WP-CLI not found." >&2; return 1; fi if ! wp core is-installed --quiet; then echo "❌ Error: This does not appear to be a WordPress installation." >&2; return 1; fi if ! command -v wget &>/dev/null; then echo "❌ Error: wget not found." >&2; return 1; fi if ! command -v unzip &>/dev/null; then echo "❌ Error: unzip not found." >&2; return 1; fi if ! command -v curl &>/dev/null; then echo "❌ Error: curl not found." >&2; return 1; fi echo "🚀 Starting WordPress Site Reset 🚀" echo "This is a destructive operation." # A 3-second countdown to allow the user to abort (Ctrl+C) for i in {3..1}; do echo -n "Continuing in $i... "; sleep 1; done; echo # --- Gather Info Before Reset --- local url; url=$( wp option get home --skip-plugins --skip-themes ) local title; title=$( wp option get blogname --skip-plugins --skip-themes ) # If admin_email is not supplied, get it from the current installation if [ -z "$admin_email" ]; then admin_email=$(wp option get admin_email --skip-plugins --skip-themes) echo "â„šī¸ Admin email not provided. Using existing email: $admin_email" fi # --- Perform Reset --- echo "Step 1/9: Resetting the database..." wp db reset --yes --skip-plugins --skip-themes echo "Step 2/9: Downloading latest WordPress core..." wp core download --force --skip-plugins --skip-themes echo "Step 3/9: Installing WordPress core..." wp core install --url="$url" --title="$title" --admin_user="$admin_user" --admin_email="$admin_email" --skip-plugins --skip-themes echo "Step 4/9: Deleting all other themes..." wp theme delete --all --force --skip-plugins --skip-themes echo "Step 5/9: Deleting all plugins..." wp plugin delete --all --skip-plugins --skip-themes echo "Step 6/9: Finding the latest default WordPress theme..." latest_default_theme=$(cat <<'PHP_SCRIPT' | wp eval-file - themes) === false) { exit(1); } $twentyThemes = []; foreach ($data->themes as $theme) { if (isset($theme->slug) && str_starts_with($theme->slug, 'twenty')) { $twentyThemes[] = $theme->slug; } } if (empty($twentyThemes)) { exit(1); } echo $twentyThemes[0]; PHP_SCRIPT ) if [ $? -ne 0 ] || [ -z "$latest_default_theme" ]; then echo "❌ Error: Could not determine the latest default theme. Aborting reset." >&2 return 1 fi echo "✅ Latest default theme is '$latest_default_theme'." echo "Step 7/9: Installing and activating '$latest_default_theme'..." wp theme install "$latest_default_theme" --force --activate --skip-plugins --skip-themes echo "Step 8/9: Cleaning up directories (mu-plugins, uploads)..." rm -rf wp-content/mu-plugins/ mkdir -p wp-content/mu-plugins/ rm -rf wp-content/uploads/ mkdir -p wp-content/uploads/ echo "Step 9/9: Installing helper plugins (Kinsta MU, CaptainCore Helper)..." if wget -q https://kinsta.com/kinsta-tools/kinsta-mu-plugins.zip; then unzip -q kinsta-mu-plugins.zip -d wp-content/mu-plugins/ rm kinsta-mu-plugins.zip echo "✅ Kinsta MU plugin installed." else echo "âš ī¸ Warning: Could not download Kinsta MU plugin." fi if curl -sSL https://run.captaincore.io/deploy-helper | bash -s; then echo "✅ CaptainCore Helper deployed." else echo "âš ī¸ Warning: Could not deploy CaptainCore Helper." fi echo "" echo "✅ WordPress reset complete!" echo " URL: $url" echo " Admin User: $admin_user" } # ---------------------------------------------------- # Resets file and folder permissions to common defaults (755 for dirs, 644 for files). # ---------------------------------------------------- function reset_permissions() { echo "Resetting file and folder permissions to defaults" find . -type d -exec chmod 755 {} \; find . -type f -exec chmod 644 {} \; echo "✅ Permissions have been reset." } # ---------------------------------------------------- # Identifies plugins that may be slowing down WP-CLI command execution. # ---------------------------------------------------- function identify_slow_plugins() { _get_wp_execution_time() { local output; output=$(command wp "$@" --debug 2>&1); echo "$output" | perl -ne '/Debug \(bootstrap\): Running command: .+\(([^s]+s)/ && print $1'; } if ! command -v wp &>/dev/null; then echo "❌ Error: WP-CLI (wp command) not found." >&2; return 1; fi if ! command wp core is-installed --quiet; then echo "❌ Error: This does not appear to be a WordPress installation." >&2; return 1; fi echo "🚀 WordPress Plugin Performance Test 🚀" echo "This script measures the execution time of 'wp plugin list --debug' under various conditions." echo "" echo "📋 Initial Baseline Measurements for 'wp plugin list --debug':" local time_no_theme_s; printf " âŗ Measuring time with NO themes loaded (--skip-themes)... "; time_no_theme_s=$(_get_wp_execution_time plugin list --skip-themes); echo "Time: $time_no_theme_s" local time_no_plugins_s; printf " âŗ Measuring time with NO plugins loaded (--skip-plugins)... "; time_no_plugins_s=$(_get_wp_execution_time plugin list --skip-plugins); echo "Time: $time_no_plugins_s" local base_time_s; printf " âŗ Measuring base time (ALL plugins & theme active)... "; base_time_s=$(_get_wp_execution_time plugin list) if [[ -z "$base_time_s" ]]; then echo "❌ Error: Could not measure base execution time." >&2; return 1; fi; echo "Base time: $base_time_s" echo "" local active_plugins=() while IFS= read -r line; do active_plugins+=("$line") done < <(command wp plugin list --field=name --status=active) if [[ ${#active_plugins[@]} -eq 0 ]]; then echo "â„šī¸ No active plugins found to test."; return 0; fi echo "📊 Measuring impact of individual plugins (compared to '${base_time_s}' base time):" echo "A larger positive 'Impact' suggests the plugin contributes more to the load time of this specific WP-CLI command." echo "---------------------------------------------------------------------------------"; printf "%-40s | %-15s | %-15s\n" "Plugin Skipped" "Time w/ Skip" "Impact (Base-Skip)"; echo "---------------------------------------------------------------------------------" local results=(); for plugin in "${active_plugins[@]}"; do local time_with_skip_s; time_with_skip_s=$(_get_wp_execution_time plugin list --skip-plugins="$plugin") if [[ -n "$time_with_skip_s" ]]; then local diff_s; diff_s=$(awk -v base="${base_time_s%s}" -v skip="${time_with_skip_s%s}" 'BEGIN { printf "%.3f", base - skip }') local impact_sign="" if [[ $(awk -v diff="$diff_s" 'BEGIN { print (diff > 0) }') -eq 1 ]]; then impact_sign="+" fi results+=("$(printf "%.3f" "$diff_s")|$plugin|$time_with_skip_s|${impact_sign}${diff_s}s") else results+=("0.000|$plugin|Error|Error measuring"); fi done local sorted_results=() while IFS= read -r line; do sorted_results+=("$line") done < <(printf "%s\n" "${results[@]}" | sort -t'|' -k1,1nr) for result_line in "${sorted_results[@]}"; do local p_name; p_name=$(echo "$result_line" | cut -d'|' -f2); local t_skip; t_skip=$(echo "$result_line" | cut -d'|' -f3); local i_str; i_str=$(echo "$result_line" | cut -d'|' -f4) printf "%-40s | %-15s | %-15s\n" "$p_name" "$t_skip" "$i_str" done echo "---------------------------------------------------------------------------------"; echo ""; echo "✅ Test Complete" echo "💡 Note: This measures impact on a specific WP-CLI command. For front-end or"; echo " admin profiling, consider using a plugin like Query Monitor or New Relic."; echo "" } # ---------------------------------------------------- # Deactivates a suspend message by removing the mu-plugin. # (Formerly site_activate) # ---------------------------------------------------- function suspend_deactivate() { local wp_content="$1" # Set default wp-content if not provided if [[ -z "$wp_content" ]]; then wp_content="wp-content" fi local suspend_file="${wp_content}/mu-plugins/captaincore-suspended.php" if [ -f "$suspend_file" ]; then echo "Deactivating suspend message by removing ${suspend_file}..." rm "$suspend_file" echo "✅ Suspend message deactivated. Site is now live." else echo "Site appears to be already live (suspend file not found)." fi # Clear Kinsta cache if environment is detected if [ -f "/etc/update-motd.d/00-kinsta-welcome" ]; then if command -v wp &> /dev/null && wp kinsta cache purge --all --skip-themes &> /dev/null; then echo "Kinsta cache purged." else echo "Warning: Could not purge Kinsta cache. Is the 'wp kinsta' command available?" >&2 fi fi } # ---------------------------------------------------- # Activates a suspend message by adding an mu-plugin. # (Formerly site_deactivate) # ---------------------------------------------------- function suspend_activate() { local name="$1" local link="$2" local wp_content="$3" # Set default wp-content if not provided if [[ -z "$wp_content" ]]; then wp_content="wp-content" fi # Check for required arguments if [[ -z "$name" || -z "$link" ]]; then echo "Error: Missing required flags for 'suspend activate'." >&2 show_command_help "suspend" return 1 fi if [ ! -d "${wp_content}/mu-plugins" ]; then echo "Creating directory: ${wp_content}/mu-plugins" mkdir -p "${wp_content}/mu-plugins" fi # Remove existing deactivation file if present if [ -f "${wp_content}/mu-plugins/captaincore-suspended.php" ]; then echo "Removing existing suspend file..." rm "${wp_content}/mu-plugins/captaincore-suspended.php" fi # Create the deactivation mu-plugin local output_file="${wp_content}/mu-plugins/captaincore-suspended.php" echo "Generating suspend file at ${output_file}..." cat < "$output_file" Website Suspended
Website Suspended

This website is currently unavailable.

Site owners may contact ${name}.

/dev/null && wp kinsta cache purge --all --skip-themes &> /dev/null; then echo "Kinsta cache purged." else echo "Warning: Could not purge Kinsta cache. Is the 'wp kinsta' command available?" >&2 fi fi } # ---------------------------------------------------- # Displays the version of the captaincore-do script. # ---------------------------------------------------- function show_version() { echo "captaincore-do version $CAPTAINCORE_DO_VERSION" } # Pass all script arguments to the main function. main "$@"