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 13:35:26 +00:00
2025-08-06 14:46:20 +00:00
info " Running wp search-replace ' $OLD_PART ' ' $NEW_PART ' … "
# Build command anew for each iteration
CMD = ( )
2025-08-06 13:35:26 +00:00
if [ [ -n " $SUDO_CMD " ] ] ; then
# shellcheck disable=SC2206
2025-08-06 14:46:20 +00:00
CMD = ( $SUDO_CMD )
2025-08-06 13:35:26 +00:00
fi
2025-08-06 14:46:20 +00:00
CMD += ( " $WP_EXECUTABLE " "search-replace" " $OLD_PART " " $NEW_PART " " ${ WP_RUN_ARGS [@] } " )
2025-08-06 13:35:26 +00:00
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
2025-08-08 16:27:38 +00:00
# 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 16:40:02 +00:00
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"
2025-08-08 16:27:38 +00:00
# Determine which entries are missing to avoid duplicates
2025-08-08 16:40:02 +00:00
needs_home = 1; grep -Eq "define\([[:space:]]*['\"]WP_HOME['\"]" " $CONFIG_FILE " && needs_home = 0 || true
needs_siteurl = 1; grep -Eq "define\([[:space:]]*['\"]WP_SITEURL['\"]" " $CONFIG_FILE " && needs_siteurl = 0 || true
needs_http_host = 1; grep -Eq "\$_SERVER\[(\"|')HTTP_HOST\1\][[:space:]]*=" " $CONFIG_FILE " && needs_http_host = 0 || true
2025-08-08 16:27:38 +00:00
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) …"
awk -v url = " $TARGET_URL " -v host = " $HOST_ONLY " -v insert_home = " $needs_home " -v insert_siteurl = " $needs_siteurl " -v insert_hostfix = " $needs_http_host " '
BEGIN{ inserted = 0}
/\/ \* That\x 27s all/{
print "\n// Added by wp-search-replace.sh" ;
if ( insert_home = = 1) print "define( \"WP_HOME\", \"" url "\" );" ;
if ( insert_siteurl = = 1) print "define( \"WP_SITEURL\", \"" url "\" );" ;
if ( insert_hostfix = = 1) print " if ( defined( \"WP_CLI\" ) && WP_CLI && ! isset( $_SERVER [\"HTTP_HOST\"] ) ) { $_SERVER [\"HTTP_HOST\"] = \" " host "\"; }" ;
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
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