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

369 lines
13 KiB
Bash
Raw Permalink 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."
# Discover CA bundle path for HTTPS requests executed by PHP/cURL
CA_BUNDLE=""
for candidate in \
/etc/pki/tls/certs/ca-bundle.crt \
/etc/ssl/certs/ca-bundle.crt \
/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
do
if [[ -f "$candidate" ]]; then
CA_BUNDLE="$candidate"
break
fi
done
if [[ -n "$CA_BUNDLE" ]]; then
info "Using CA bundle at: $CA_BUNDLE"
ENV_PREFIX=(env SSL_CERT_FILE="$CA_BUNDLE" CURL_CA_BUNDLE="$CA_BUNDLE")
else
warning "Could not locate a system CA bundle; HTTPS requests may fail."
ENV_PREFIX=()
fi
# --- 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
# Prepend environment variables to ensure cURL trusts system CA store
if [[ ${#ENV_PREFIX[@]} -gt 0 ]]; then
CMD+=("${ENV_PREFIX[@]}")
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)
# 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
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\[["\x27]HTTP_HOST["\x27]\][[: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 by emptying the filesystem cache directory
CACHE_DIR="/var/www/webroot/.cache"
info "Purging LiteSpeed cache by emptying '$CACHE_DIR' …"
if [[ -d "$CACHE_DIR" ]]; then
# Build deletion command with root privileges if available
DEL_CMD=()
if [[ "$(id -u)" -eq 0 ]]; then
:
elif sudo -n true 2>/dev/null; then
DEL_CMD=(sudo)
elif [[ -w "$CACHE_DIR" ]]; then
:
else
warning "Insufficient permissions to modify '$CACHE_DIR'. Please run this script as root or with passwordless sudo."
fi
# Safely delete only the contents (including dotfiles) without removing the directory itself
if [[ ${#DEL_CMD[@]} -gt 0 ]]; then
"${DEL_CMD[@]}" find "$CACHE_DIR" -mindepth 1 -maxdepth 1 -exec rm -rf {} + || \
warning "Failed to fully clear '$CACHE_DIR'."
else
find "$CACHE_DIR" -mindepth 1 -maxdepth 1 -exec rm -rf {} + || \
warning "Failed to fully clear '$CACHE_DIR'."
fi
success "LiteSpeed cache directory cleared."
else
warning "LiteSpeed cache directory '$CACHE_DIR' does not exist; nothing to clear."
fi
# 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
if [[ ${#ENV_PREFIX[@]} -gt 0 ]]; then
TRANS_CMD+=("${ENV_PREFIX[@]}")
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
if [[ ${#ENV_PREFIX[@]} -gt 0 ]]; then
FLUSH_CMD+=("${ENV_PREFIX[@]}")
fi
FLUSH_CMD+=("$WP_EXECUTABLE" "cache" "flush" "${WP_RUN_ARGS[@]}")
"${FLUSH_CMD[@]}" || warning "Cache flush command returned non-zero exit status."
exit $STATUS