mb-admin/scripts/wp-search-replace.sh

312 lines
11 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/bin/bash
#
# Wrapper script for WP-CLI's search-replace command.
# Safely runs 'wp search-replace' with the correct user and path context.
#
# --- Configuration ---
set -e
set -u
set -o pipefail
# Colors for output messages
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# --- Default Values ---
WP_ROOT="/var/www/webroot/ROOT" # Default WordPress root directory
WEB_USER="litespeed" # Default web server user
WP_CLI_PATH="/usr/local/bin/wp" # Default path to WP-CLI
# --- Helper Functions ---
# Print informational messages
info() {
printf "${BLUE}[INFO] %s${NC}\n" "$@"
}
# Print success messages
success() {
printf "${GREEN}[SUCCESS] %s${NC}\n" "$@"
}
# Print warning messages
warning() {
printf "${YELLOW}[WARNING] %s${NC}\n" "$@"
}
# Print error messages and exit
error_exit() {
printf "${RED}[ERROR] %s${NC}\n" "$@" >&2
exit 1
}
# Function to display usage information
usage() {
printf "Usage: %s <search> <replace> [options...]\n" "$0"
printf "\n"
printf "A wrapper for the 'wp search-replace' command that automatically handles user and path context.\n"
printf "All options after <replace> are passed directly to 'wp search-replace'.\n"
printf "\n"
printf "Required Arguments:\n"
printf " <search> The string to search for.\n"
printf " <replace> The string to replace it with.\n"
printf "\n"
printf "Common WP-CLI Options (pass these after <replace>):\n"
printf " [<table>...] One or more database tables to restrict the search to.\n"
printf " --dry-run Run the search-replace without making any changes.\n"
printf " --all-tables Search through all tables registered to \$wpdb.\n"
printf " --network Search and replace through all sites in a multisite network.\n"
printf " --recurse-objects Enable recursing into objects to replace strings.\n"
printf " --report-changed-only Only report changes, not every table checked.\n"
printf " --skip-columns=<cols> Comma-separated list of columns to skip.\n"
printf "\n"
printf "Wrapper-Specific Options:\n"
printf " --wproot=PATH Override the default WordPress root directory (%s).\n" "$WP_ROOT"
printf " --webuser=USER Override the default web server user (%s).\n" "$WEB_USER"
printf " -h, --help Display this help message.\n"
printf "\n"
printf "Example:\n"
printf " %s 'http://old-domain.com' 'https://new-domain.com' --all-tables --dry-run\n" "$0"
exit 1
}
# --- Argument Parsing ---
# Separate wrapper arguments from WP-CLI arguments
WP_CLI_ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
;;
--wproot=*)
WP_ROOT="${1#*=}"
shift
;;
--webuser=*)
WEB_USER="${1#*=}"
shift
;;
*)
# Collect all other arguments for WP-CLI
WP_CLI_ARGS+=("$1")
shift
;;
esac
done
# Check for mandatory arguments
if [[ ${#WP_CLI_ARGS[@]} -lt 2 ]]; then
error_exit "Missing required <search> and <replace> arguments."
fi
info "Wrapper settings: WP_ROOT='${WP_ROOT}', WEB_USER='${WEB_USER}'"
info "Arguments passed to WP-CLI: ${WP_CLI_ARGS[*]}"
# --- Prerequisite Checks ---
info "Checking prerequisites..."
if [[ ! -d "$WP_ROOT" ]]; then error_exit "WordPress root directory '$WP_ROOT' does not exist."; fi
if ! command -v "$WP_CLI_PATH" &> /dev/null; then
warning "WP-CLI not found at $WP_CLI_PATH."
if ! command -v "wp" &> /dev/null; then
error_exit "WP-CLI not found at $WP_CLI_PATH or in system PATH. Please install WP-CLI."
else
WP_CLI_PATH="wp" # Fallback to wp in path
info "Found 'wp' in system PATH. Using that."
fi
fi
if ! id "$WEB_USER" &>/dev/null; then error_exit "Web user '$WEB_USER' does not exist."; fi
success "Prerequisites seem OK."
# --- WP-CLI Execution Context Setup ---
# This logic is adapted from install-wordpress.sh to ensure commands run as the correct user.
WP_RUN_ARGS=("--path=$WP_ROOT")
SUDO_CMD=""
WP_EXECUTABLE="$WP_CLI_PATH"
if [[ "$(id -u)" -eq 0 ]]; then
info "Script is running as root. Attempting to run WP-CLI as '$WEB_USER'."
# Use 'sudo -n' to check for passwordless sudo access.
if sudo -n -u "$WEB_USER" "$WP_EXECUTABLE" --info --skip-update --quiet "${WP_RUN_ARGS[@]}" &>/dev/null; then
SUDO_CMD="sudo -u $WEB_USER"
info "WP-CLI will be executed as '$WEB_USER'."
else
warning "Could not execute WP-CLI as '$WEB_USER' without a password. Falling back to running as root."
WP_RUN_ARGS+=("--allow-root")
fi
else
if [[ "$(id -un)" != "$WEB_USER" ]]; then
info "Script is running as non-root user '$(id -un)'. Checking for sudo access to run as '$WEB_USER'."
if sudo -n -u "$WEB_USER" "$WP_EXECUTABLE" --info --skip-update --quiet "${WP_RUN_ARGS[@]}" &>/dev/null; then
SUDO_CMD="sudo -u $WEB_USER"
info "Successfully configured sudo execution for WP-CLI as '$WEB_USER'."
else
error_exit "Unable to execute WP-CLI as '$WEB_USER'. Ensure the current user has passwordless sudo access, or run this script as '$WEB_USER' or 'root'."
fi
else
info "Script is already running as the web user ('$WEB_USER')."
fi
fi
info "WP-CLI final execution command prefix: [${SUDO_CMD:-direct}]"
# --- Domain normalization helper ---
trim_trailing_slash() {
local str="$1"
[[ "$str" == */ ]] && str="${str%/}"
printf '%s' "$str"
}
OLD_URL="${WP_CLI_ARGS[0]}"
NEW_URL="${WP_CLI_ARGS[1]}"
OLD_NORMAL=$(trim_trailing_slash "$OLD_URL")
NEW_NORMAL=$(trim_trailing_slash "$NEW_URL")
# Array of replacement tasks (each element "old|||new")
TASKS=("$OLD_URL|||$NEW_URL")
if [[ "$OLD_URL" != "$OLD_NORMAL" ]]; then
# also replace variant without trailing slash
TASKS+=("$OLD_NORMAL|||$NEW_NORMAL")
fi
# --- Execute Command ---
info "Executing 'wp search-replace'..."
# Build command array safely to preserve argument quoting
CMD=()
if [[ -n "$SUDO_CMD" ]]; then
# shellcheck disable=SC2206
CMD=($SUDO_CMD) # split sudo command into array elements
fi
CMD+=("$WP_EXECUTABLE" "search-replace")
CMD+=("${WP_CLI_ARGS[@]}")
CMD+=("${WP_RUN_ARGS[@]}")
for pair in "${TASKS[@]}"; do
OLD_PART=${pair%%|||*}
NEW_PART=${pair##*|||}
info "Running wp search-replace '$OLD_PART' '$NEW_PART' …"
# Build command anew for each iteration
CMD=()
if [[ -n "$SUDO_CMD" ]]; then
# shellcheck disable=SC2206
CMD=($SUDO_CMD)
fi
CMD+=("$WP_EXECUTABLE" "search-replace" "$OLD_PART" "$NEW_PART" "${WP_RUN_ARGS[@]}")
SEARCH_OUTPUT=$( "${CMD[@]}" 2>&1 )
STATUS=$?
printf "%s\n" "$SEARCH_OUTPUT"
if [[ $STATUS -ne 0 ]]; then
error_exit "wp search-replace failed for pattern '$OLD_PART' -> '$NEW_PART' (exit $STATUS)."
fi
done
success "All search-replace tasks completed successfully."
# -----------------------------------------------------------------
# Update wp-config.php WP_HOME and WP_SITEURL if they exist
# -----------------------------------------------------------------
CONFIG_FILE="$WP_ROOT/wp-config.php"
if [[ -f "$CONFIG_FILE" && -w "$CONFIG_FILE" ]]; then
info "Updating WP_HOME and WP_SITEURL in wp-config.php …"
# Use the new URL without trailing slash, ensure it has https://
TARGET_URL="$NEW_NORMAL"
# Ensure URL starts with https://
if [[ ! "$TARGET_URL" =~ ^https:// ]]; then
TARGET_URL="https://$TARGET_URL"
info "Added https:// prefix to URL: $TARGET_URL"
fi
# Derive host-only from TARGET_URL for HTTP_HOST usage
HOST_ONLY="${TARGET_URL#*://}"
HOST_ONLY="${HOST_ONLY%%/*}"
[[ -z "$HOST_ONLY" ]] && HOST_ONLY="$TARGET_URL"
# shellcheck disable=SC2016 # we want literal quotes inside sed replacement
# Replace existing definitions (handle both single and double quotes)
sed -i -E "s|define\([[:space:]]*['\"]WP_HOME['\"][[:space:]]*,[[:space:]]*['\"][^'\"]*['\"][[:space:]]*\);|define( 'WP_HOME', '${TARGET_URL}' );|; s|define\([[:space:]]*['\"]WP_SITEURL['\"][[:space:]]*,[[:space:]]*['\"][^'\"]*['\"][[:space:]]*\);|define( 'WP_SITEURL', '${TARGET_URL}' );|; s|\$_SERVER\[(\"|')HTTP_HOST\1\][[:space:]]*=[[:space:]]*(\"|')[^"']*\2;|\$_SERVER['HTTP_HOST'] = '${HOST_ONLY}';|" "$CONFIG_FILE" || warning "Failed to update wp-config.php"
# Determine which entries are missing to avoid duplicates
needs_home=1
needs_siteurl=1
needs_http_host=1
if grep -Eq "define[[:space:]]*\([[:space:]]*['\"]WP_HOME['\"]" "$CONFIG_FILE"; then
needs_home=0
fi
if grep -Eq "define[[:space:]]*\([[:space:]]*['\"]WP_SITEURL['\"]" "$CONFIG_FILE"; then
needs_siteurl=0
fi
if grep -Eq "\$_SERVER\[(\"|')HTTP_HOST\1\][[:space:]]*=" "$CONFIG_FILE"; then
needs_http_host=0
fi
if [[ $needs_home -eq 1 || $needs_siteurl -eq 1 || $needs_http_host -eq 1 ]]; then
info "Inserting missing wp-config.php directives (WP_HOME/WP_SITEURL/HTTP_HOST)"
INSERT_BLOCK="\n// Added by wp-search-replace.sh\n"
if [[ $needs_home -eq 1 ]]; then
INSERT_BLOCK+="define( 'WP_HOME', '${TARGET_URL}' );\n"
fi
if [[ $needs_siteurl -eq 1 ]]; then
INSERT_BLOCK+="define( 'WP_SITEURL', '${TARGET_URL}' );\n"
fi
if [[ $needs_http_host -eq 1 ]]; then
INSERT_BLOCK+="if ( defined( 'WP_CLI' ) && WP_CLI && ! isset( $_SERVER['HTTP_HOST'] ) ) { $_SERVER['HTTP_HOST'] = '${HOST_ONLY}'; }\n"
fi
awk -v block="$INSERT_BLOCK" '
BEGIN { inserted=0 }
/\/\* That/ {
printf "%s", block
inserted=1
}
{ print }
END { if (!inserted) exit 1 }
' "$CONFIG_FILE" > "$CONFIG_FILE.tmp" && mv "$CONFIG_FILE.tmp" "$CONFIG_FILE" || warning "Could not insert directives into wp-config.php"
fi
else
warning "wp-config.php not found or not writable; skipped WP_HOME / WP_SITEURL update."
fi
# Purge LiteSpeed Cache (if plugin installed)
info "Attempting LiteSpeed Cache purge (wp litespeed-purge all)"
LS_CMD=()
if [[ -n "$SUDO_CMD" ]]; then
# shellcheck disable=SC2206
LS_CMD=($SUDO_CMD)
fi
LS_CMD+=("$WP_EXECUTABLE" "litespeed-purge" "all" "${WP_RUN_ARGS[@]}")
"${LS_CMD[@]}" || warning "LiteSpeed purge returned non-zero exit status (plugin may be inactive)."
# Delete all transients recommended after domain/URL migration
info "Deleting all transients (wp transient delete --all)..."
TRANS_CMD=()
if [[ -n "$SUDO_CMD" ]]; then
# shellcheck disable=SC2206
TRANS_CMD=($SUDO_CMD)
fi
TRANS_CMD+=("$WP_EXECUTABLE" "transient" "delete" "--all" "${WP_RUN_ARGS[@]}")
"${TRANS_CMD[@]}" || warning "Transient delete command returned non-zero exit status."
# Flush object/cache to ensure changes propagate immediately
info "Flushing WordPress caches (wp cache flush)..."
FLUSH_CMD=()
if [[ -n "$SUDO_CMD" ]]; then
# shellcheck disable=SC2206
FLUSH_CMD=($SUDO_CMD)
fi
FLUSH_CMD+=("$WP_EXECUTABLE" "cache" "flush" "${WP_RUN_ARGS[@]}")
"${FLUSH_CMD[@]}" || warning "Cache flush command returned non-zero exit status."
exit $STATUS