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

319 lines
11 KiB
Bash
Raw Normal View History

2025-08-06 13:24:04 +00:00
#!/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}]"
2025-08-06 14:46:20 +00:00
# --- 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
2025-08-06 13:24:04 +00:00
# --- 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[@]}")
2025-08-06 14:46:20 +00:00
for pair in "${TASKS[@]}"; do
OLD_PART=${pair%%|||*}
NEW_PART=${pair##*|||}
2025-08-06 14:46:20 +00:00
info "Running wp search-replace '$OLD_PART' '$NEW_PART' …"
# Build command anew for each iteration
CMD=()
if [[ -n "$SUDO_CMD" ]]; then
# shellcheck disable=SC2206
2025-08-06 14:46:20 +00:00
CMD=($SUDO_CMD)
fi
2025-08-06 14:46:20 +00:00
CMD+=("$WP_EXECUTABLE" "search-replace" "$OLD_PART" "$NEW_PART" "${WP_RUN_ARGS[@]}")
2025-08-06 14:46:20 +00:00
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)."
2025-08-06 13:24:04 +00:00
fi
2025-08-06 14:46:20 +00:00
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 …"
2025-08-07 09:52:00 +00:00
# Use the new URL without trailing slash, ensure it has https://
2025-08-06 14:46:20 +00:00
TARGET_URL="$NEW_NORMAL"
2025-08-07 09:52:00 +00:00
# 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)
2025-08-08 17:13:54 +00:00
# Update WP_HOME if it exists
sed -i -E "s|define[[:space:]]*\([[:space:]]*['\"]WP_HOME['\"][[:space:]]*,[[:space:]]*['\"][^'\"]*['\"][[:space:]]*\)|define( 'WP_HOME', '${TARGET_URL}' )|g" "$CONFIG_FILE"
# Update WP_SITEURL if it exists
sed -i -E "s|define[[:space:]]*\([[:space:]]*['\"]WP_SITEURL['\"][[:space:]]*,[[:space:]]*['\"][^'\"]*['\"][[:space:]]*\)|define( 'WP_SITEURL', '${TARGET_URL}' )|g" "$CONFIG_FILE"
# Update HTTP_HOST if it exists
sed -i -E "s|(\\\$_SERVER\[['\"]HTTP_HOST['\"]\][[:space:]]*=[[:space:]]*['\"])[^'\"]*(['\"];)|\1${HOST_ONLY}\2|g" "$CONFIG_FILE"
# Determine which entries are missing to avoid duplicates
2025-08-08 16:52:02 +00:00
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
2025-08-08 17:13:54 +00:00
if grep -Eq '\$_SERVER\[["\x27]HTTP_HOST["\x27]\][[:space:]]*=' "$CONFIG_FILE"; then
2025-08-08 16:52:02 +00:00
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) …"
2025-08-08 17:06:32 +00:00
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
}
2025-08-08 17:06:32 +00:00
{ 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
2025-08-06 13:24:04 +00:00
else
2025-08-06 14:46:20 +00:00
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)
2025-08-06 13:24:04 +00:00
fi
2025-08-06 14:46:20 +00:00
FLUSH_CMD+=("$WP_EXECUTABLE" "cache" "flush" "${WP_RUN_ARGS[@]}")
"${FLUSH_CMD[@]}" || warning "Cache flush command returned non-zero exit status."
2025-08-06 13:24:04 +00:00
exit $STATUS