Revised shell script to fix sudo errors

main
Anthony 2025-04-05 01:23:26 +08:00
parent 9af1e7ac84
commit 61114d55d1
1 changed files with 282 additions and 188 deletions

View File

@ -3,6 +3,7 @@
# Script to automate WordPress installation. # Script to automate WordPress installation.
# Includes dependency checks, WP-CLI installation, database setup, # Includes dependency checks, WP-CLI installation, database setup,
# WordPress core installation, and configuration. # WordPress core installation, and configuration.
# v2.1 - Fixes WP-CLI execution path issues when run as root.
# #
# --- Configuration --- # --- Configuration ---
@ -22,8 +23,6 @@ BLUE='\033[0;34m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
# --- Default Values --- # --- Default Values ---
# Avoid insecure defaults. Consider making these mandatory or generating random ones.
# Leaving them blank to force user input or argument passing.
WP_ADMIN_USER="" WP_ADMIN_USER=""
WP_ADMIN_PASS="" WP_ADMIN_PASS=""
WP_ADMIN_EMAIL="" WP_ADMIN_EMAIL=""
@ -32,9 +31,10 @@ DOMAIN="" # Domain will be determined or required
DB_HOST="127.0.0.1" DB_HOST="127.0.0.1"
DB_ROOT_USER="root" DB_ROOT_USER="root"
DB_ROOT_PASS="" # Require user to provide this for security DB_ROOT_PASS="" # Require user to provide this for security
SKIP_DB_ROOT_RESET="false" # By default, perform the root password reset (use --skip-db-root-reset to disable) # PERFORM_DB_ROOT_RESET is set during arg parsing
WEB_USER="litespeed" # Web server user (e.g., www-data, apache, nginx) WEB_USER="litespeed" # Web server user
WEB_GROUP="litespeed" # Web server group WEB_GROUP="litespeed" # Web server group
WP_CLI_PATH="/usr/local/bin/wp" # Path to WP-CLI executable
# --- Helper Functions --- # --- Helper Functions ---
@ -56,6 +56,8 @@ warning() {
# Print error messages and exit # Print error messages and exit
error_exit() { error_exit() {
printf "${RED}[ERROR] %s${NC}\n" "$@" >&2 printf "${RED}[ERROR] %s${NC}\n" "$@" >&2
# Attempt cleanup before exiting
cleanup &> /dev/null || true
exit 1 exit 1
} }
@ -69,7 +71,8 @@ usage() {
printf " --wpusername=USERNAME WordPress admin username (mandatory)\n" printf " --wpusername=USERNAME WordPress admin username (mandatory)\n"
printf " --wppassword=PASSWORD WordPress admin password (mandatory)\n" printf " --wppassword=PASSWORD WordPress admin password (mandatory)\n"
printf " --wpemail=EMAIL WordPress admin email (mandatory)\n" printf " --wpemail=EMAIL WordPress admin email (mandatory)\n"
printf " --dbrootpass=PASSWORD Current MySQL/MariaDB root password (mandatory unless resetting)\n" # Make dbrootpass conditional in description
printf " --dbrootpass=PASSWORD Current MySQL/MariaDB root password (required IF NOT using --reset-db-root-pass)\n"
printf "\n" printf "\n"
printf "Optional Options:\n" printf "Optional Options:\n"
printf " --wproot=PATH WordPress installation directory (default: %s)\n" "$WP_ROOT" printf " --wproot=PATH WordPress installation directory (default: %s)\n" "$WP_ROOT"
@ -77,7 +80,7 @@ usage() {
printf " --webuser=USER Web server user (default: %s)\n" "$WEB_USER" printf " --webuser=USER Web server user (default: %s)\n" "$WEB_USER"
printf " --webgroup=GROUP Web server group (default: %s)\n" "$WEB_GROUP" printf " --webgroup=GROUP Web server group (default: %s)\n" "$WEB_GROUP"
printf " --dbhost=HOST Database host (default: %s)\n" "$DB_HOST" printf " --dbhost=HOST Database host (default: %s)\n" "$DB_HOST"
printf " --reset-db-root-pass Perform the risky root password reset (requires sudo without password)\n" printf " --reset-db-root-pass Perform the risky root password reset (requires script runner with root privileges)\n"
printf " -h, --help Display this help message\n" printf " -h, --help Display this help message\n"
printf "\n" printf "\n"
printf "Example:\n" printf "Example:\n"
@ -93,28 +96,30 @@ command_exists() {
# Function to generate a random secure password # Function to generate a random secure password
generate_password() { generate_password() {
openssl rand -base64 16 # Increased length slightly openssl rand -base64 16
} }
# Function to clean up temporary files # Function to clean up temporary files
# Define WP_CLI_CONFIG_PATH early so cleanup function knows about it
WP_CLI_CONFIG_PATH="/tmp/wp-cli-config-$RANDOM.yml"
cleanup() { cleanup() {
info "Cleaning up temporary files..." # Check if file exists before trying to remove
if [[ -n "${WP_CLI_CONFIG_PATH-}" && -f "$WP_CLI_CONFIG_PATH" ]]; then
info "Cleaning up temporary WP-CLI config: $WP_CLI_CONFIG_PATH"
rm -f "$WP_CLI_CONFIG_PATH" rm -f "$WP_CLI_CONFIG_PATH"
# Add any other cleanup tasks here fi
} }
# --- Argument Parsing --- # --- Argument Parsing ---
# Using getopt for better argument handling
TEMP=$(getopt -o h --longoptions help,wpusername:,wppassword:,wpemail:,wproot:,domain:,dbhost:,dbrootpass:,reset-db-root-pass,webuser:,webgroup: -n "$0" -- "$@") TEMP=$(getopt -o h --longoptions help,wpusername:,wppassword:,wpemail:,wproot:,domain:,dbhost:,dbrootpass:,reset-db-root-pass,webuser:,webgroup: -n "$0" -- "$@")
if [ $? != 0 ]; then if [ $? != 0 ]; then
error_exit "Terminating... Invalid arguments." error_exit "Terminating... Invalid arguments provided."
fi fi
# Note the quotes around "$TEMP": they are essential!
eval set -- "$TEMP" eval set -- "$TEMP"
unset TEMP unset TEMP
PERFORM_DB_ROOT_RESET="false" PERFORM_DB_ROOT_RESET="false" # Default value
while true; do while true; do
case "$1" in case "$1" in
@ -129,90 +134,161 @@ while true; do
--webuser) WEB_USER="$2"; shift 2 ;; --webuser) WEB_USER="$2"; shift 2 ;;
--webgroup) WEB_GROUP="$2"; shift 2 ;; --webgroup) WEB_GROUP="$2"; shift 2 ;;
-h|--help) usage ;; -h|--help) usage ;;
--) shift ; break ;; # End of options --) shift ; break ;;
*) error_exit "Internal error! Unexpected option: $1";; *) error_exit "Internal error parsing options! Unexpected option: $1";;
esac esac
done done
# Set trap *after* WP_CLI_CONFIG_PATH is defined and potentially used
trap cleanup EXIT SIGINT SIGTERM
# --- Validation --- # --- Validation ---
info "Validating parameters..." info "Validating parameters..."
if [[ -z "$WP_ADMIN_USER" ]]; then error_exit "WordPress admin username (--wpusername) is required."; fi if [[ -z "$WP_ADMIN_USER" ]]; then error_exit "WordPress admin username (--wpusername) is required."; fi
if [[ -z "$WP_ADMIN_PASS" ]]; then error_exit "WordPress admin password (--wppassword) is required."; fi if [[ -z "$WP_ADMIN_PASS" ]]; then error_exit "WordPress admin password (--wppassword) is required."; fi
if [[ -z "$WP_ADMIN_EMAIL" ]]; then error_exit "WordPress admin email (--wpemail) is required."; fi if [[ -z "$WP_ADMIN_EMAIL" ]]; then error_exit "WordPress admin email (--wpemail) is required."; fi
if [[ ! "$WP_ADMIN_EMAIL" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then error_exit "Invalid email format for --wpemail."; fi if [[ ! "$WP_ADMIN_EMAIL" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then error_exit "Invalid email format for --wpemail: '$WP_ADMIN_EMAIL'"; fi
if [[ "$PERFORM_DB_ROOT_RESET" == "false" && -z "$DB_ROOT_PASS" ]]; then error_exit "Database root password (--dbrootpass) is required unless --reset-db-root-pass is used."; fi if [[ "$PERFORM_DB_ROOT_RESET" == "false" && -z "$DB_ROOT_PASS" ]]; then error_exit "Database root password (--dbrootpass) is required unless --reset-db-root-pass is used."; fi
if [[ "$PERFORM_DB_ROOT_RESET" == "true" && -n "$DB_ROOT_PASS" ]]; then warning "Both --reset-db-root-pass and --dbrootpass provided. Will perform reset and ignore provided root password."; fi if [[ "$PERFORM_DB_ROOT_RESET" == "true" && -n "$DB_ROOT_PASS" ]]; then warning "Both --reset-db-root-pass and --dbrootpass provided. Reset will be performed, provided password ignored."; fi
if [[ ! -d "$WP_ROOT" ]]; then error_exit "WordPress root directory '$WP_ROOT' does not exist or is not a directory."; fi if [[ ! -d "$WP_ROOT" ]]; then error_exit "WordPress root directory '$WP_ROOT' does not exist or is not a directory."; fi
if ! id "$WEB_USER" &>/dev/null; then error_exit "Web user '$WEB_USER' does not exist."; fi if ! id "$WEB_USER" &>/dev/null; then error_exit "Web user '$WEB_USER' does not exist."; fi
if ! getent group "$WEB_GROUP" &>/dev/null; then error_exit "Web group '$WEB_GROUP' does not exist."; fi if ! getent group "$WEB_GROUP" &>/dev/null; then error_exit "Web group '$WEB_GROUP' does not exist."; fi
success "Parameters validated."
# --- Determine Domain --- # --- Determine Domain ---
if [[ -z "$DOMAIN" ]]; then if [[ -z "$DOMAIN" ]]; then
if ! command_exists hostname; then error_exit "'hostname' command not found. Please specify --domain."; fi if ! command_exists hostname; then error_exit "'hostname' command not found. Please install it or specify --domain."; fi
FULL_HOSTNAME=$(hostname -f) FULL_HOSTNAME=$(hostname -f)
# Attempt to remove common node prefixes, make this more robust if needed DOMAIN=$(echo "$FULL_HOSTNAME" | sed -E 's/^(node[0-9]*-|wp-|web-|host-|localhost)//') # Slightly more aggressive cleaning
DOMAIN=$(echo "$FULL_HOSTNAME" | sed -E 's/^(node[0-9]*-|wp-|web-|host-)//') if [[ -z "$DOMAIN" || "$DOMAIN" == "$FULL_HOSTNAME" || "$DOMAIN" == "localdomain" ]]; then # Handle cases where sed didn't change much
if [[ -z "$DOMAIN" || "$DOMAIN" == "$FULL_HOSTNAME" ]]; then warning "Could not reliably determine a public domain from hostname '$FULL_HOSTNAME'. Using it as is."
warning "Could not reliably determine domain from hostname '$FULL_HOSTNAME'. Using it as is."
DOMAIN="$FULL_HOSTNAME" DOMAIN="$FULL_HOSTNAME"
# Consider erroring out if domain detection is critical and fails
# error_exit "Failed to determine domain automatically. Please specify using --domain."
fi fi
info "Auto-detected domain: $DOMAIN (Use --domain to override)" info "Auto-detected domain: $DOMAIN (Use --domain to override if incorrect)"
else else
info "Using specified domain: $DOMAIN" info "Using specified domain: $DOMAIN"
fi fi
# --- Dependency Checks --- # --- Dependency Checks ---
info "Checking dependencies..." info "Checking dependencies..."
declare -a dependencies=("php" "mysql" "curl" "openssl" "sudo" "hostname" "sed" "systemctl" "getopt") # Group related checks
for cmd in "${dependencies[@]}"; do if ! command_exists php; then error_exit "'php' command not found. Please install PHP."; fi
if ! command_exists "$cmd"; then if ! command_exists mysql; then error_exit "'mysql' command (client) not found. Please install MySQL/MariaDB client."; fi
error_exit "Required command '$cmd' is not installed. Please install it first." if ! command_exists curl; then error_exit "'curl' command not found. Please install curl."; fi
fi if ! command_exists openssl; then error_exit "'openssl' command not found. Please install openssl."; fi
done if ! command_exists getopt; then error_exit "'getopt' command not found. Please install getopt."; fi
# Specific check for pkill if reset is chosen if ! command_exists hostname; then error_exit "'hostname' command not found. Please install hostname or provide --domain."; fi
if ! command_exists sed; then error_exit "'sed' command not found. Please install sed."; fi
# Sudo needed for WP-CLI install/move and potentially for DB reset/permissions
if ! command_exists sudo; then error_exit "'sudo' command not found. Sudo is required."; fi
# Checks specific to DB reset path
if [[ "$PERFORM_DB_ROOT_RESET" == "true" ]]; then if [[ "$PERFORM_DB_ROOT_RESET" == "true" ]]; then
if ! command_exists systemctl; then error_exit "'systemctl' command not found, but required for --reset-db-root-pass."; fi
if ! command_exists pkill; then error_exit "'pkill' command not found, but required for --reset-db-root-pass."; fi if ! command_exists pkill; then error_exit "'pkill' command not found, but required for --reset-db-root-pass."; fi
if ! command_exists mysqld_safe; then error_exit "'mysqld_safe' command not found, but required for --reset-db-root-pass."; fi # Check for mysqld_safe OR the actual daemon binary, as mysqld_safe might be deprecated
if ! command_exists mysqld_safe && ! command_exists mariadbd && ! command_exists mysqld; then
error_exit "'mysqld_safe' or 'mariadbd'/'mysqld' not found, required for --reset-db-root-pass.";
fi
# Check if we actually *can* use sudo for systemctl - requires root privileges
if [[ "$(id -u)" -ne 0 ]] && ! sudo -n systemctl is-active mariadb &>/dev/null; then
warning "Cannot run 'sudo systemctl' without password. DB reset requires script runner with root privileges or passwordless sudo for systemctl."
# Consider making this an error_exit depending on strictness
fi
fi fi
success "All dependencies found." success "All essential dependencies found."
# --- WP-CLI Setup --- # --- WP-CLI Setup ---
WP_CLI_PATH="/usr/local/bin/wp" # Check if WP-CLI executable exists at the defined path
if [[ ! -x "$WP_CLI_PATH" ]]; then
# Check if WP-CLI is installed and executable info "WP-CLI not found at $WP_CLI_PATH or not executable. Attempting installation..."
if ! command_exists wp; then TEMP_WP_CLI="./wp-cli.phar"
info "WP-CLI not found. Installing WP-CLI..." if ! curl -o "$TEMP_WP_CLI" https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar; then
if ! curl -o wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar; then
error_exit "Failed to download WP-CLI." error_exit "Failed to download WP-CLI."
fi fi
chmod +x wp-cli.phar chmod +x "$TEMP_WP_CLI"
if ! sudo mv wp-cli.phar "$WP_CLI_PATH"; then # Attempt to move using sudo. This requires sudo privileges.
error_exit "Failed to move WP-CLI to $WP_CLI_PATH. Check sudo permissions." if ! sudo mv "$TEMP_WP_CLI" "$WP_CLI_PATH"; then
# Cleanup downloaded file if move failed
rm -f "$TEMP_WP_CLI"
error_exit "Failed to move WP-CLI to $WP_CLI_PATH. Check sudo permissions for the move command."
fi fi
# Verify installation # Verify installation by checking the target path again
if ! command_exists wp; then if [[ ! -x "$WP_CLI_PATH" ]]; then
error_exit "WP-CLI installation failed unexpectedly." error_exit "WP-CLI installation failed unexpectedly. $WP_CLI_PATH not found or not executable after move."
fi fi
success "WP-CLI installed successfully to $WP_CLI_PATH" success "WP-CLI installed successfully to $WP_CLI_PATH"
else else
success "WP-CLI is already installed." success "WP-CLI is already installed at $WP_CLI_PATH."
fi fi
# Set up temporary WP-CLI config for HTTP_HOST if needed # --- WP-CLI Execution Context Setup ---
export WP_CLI_CONFIG_PATH="/tmp/wp-cli-config-$RANDOM.yml" # Determine how WP-CLI commands should be run (user, flags)
cat > "$WP_CLI_CONFIG_PATH" <<EOF # This logic prioritizes running as root if available, to avoid sudo PATH issues.
# Temporary config to help WP-CLI resolve the site URL WP_RUN_ARGS=("--path=$WP_ROOT")
# This might not always be necessary depending on server config SUDO_CMD="" # Command prefix (e.g., sudo -u user) - empty by default
WP_EXECUTABLE="$WP_CLI_PATH" # Use the full path to wp-cli
# Check if the script is running as root (UID 0)
if [[ "$(id -u)" -eq 0 ]]; then
info "Script is running as root. Using --allow-root for WP-CLI commands."
# Avoid adding flag if somehow already present (e.g., future modification)
if [[ ! " ${WP_RUN_ARGS[@]} " =~ " --allow-root " ]]; then
WP_RUN_ARGS+=("--allow-root")
fi
# No SUDO_CMD needed, root executes directly
else
# Script is NOT running as root. Check if it's running as the target web user.
if [[ "$(id -u)" -eq "$(id -u "$WEB_USER")" ]]; then
info "Script is running as the web user ('$WEB_USER'). No sudo or --allow-root needed."
# No SUDO_CMD needed, correct user executes directly
else
# Running as a different non-root user. Need to try `sudo -u WEB_USER`.
info "Script running as non-root user '$(id -un)'. Attempting to run WP-CLI as '$WEB_USER' via sudo."
if sudo -n -u "$WEB_USER" "$WP_EXECUTABLE" --info --skip-update --quiet "${WP_RUN_ARGS[@]}" &>/dev/null; then
info "Successfully verified ability to run WP-CLI as '$WEB_USER' using sudo."
SUDO_CMD="sudo -u $WEB_USER"
# Keep WP_EXECUTABLE as full path
else
# Cannot run as root, cannot run as web_user, cannot sudo -u web_user without password.
error_exit "Script lacks permissions. Run as root, as '$WEB_USER', or ensure user '$(id -un)' has passwordless sudo access to run '$WP_EXECUTABLE' as '$WEB_USER'."
fi
fi
fi
info "WP-CLI execution command prefix: [${SUDO_CMD:-direct}] using executable [$WP_EXECUTABLE]"
info "WP-CLI arguments: ${WP_RUN_ARGS[*]}"
# --- Temporary WP-CLI Config for Domain ---
# Create config file for WP-CLI to potentially pick up domain context
# This helps if WP-CLI struggles to identify the site URL, especially with --allow-root
# Might not be strictly necessary if WP_HOME/WP_SITEURL are defined early enough
# but provides extra robustness.
info "Creating temporary WP-CLI config at $WP_CLI_CONFIG_PATH"
cat > "$WP_CLI_CONFIG_PATH" <<EOF || error_exit "Failed to create temporary WP-CLI config file."
# Temporary config for WP-CLI execution context
# Ensures WP-CLI understands the target domain, especially when run via cron or as root.
apache_modules: apache_modules:
- mod_rewrite - mod_rewrite
_: _:
server: server:
HTTP_HOST: $DOMAIN HTTP_HOST: $DOMAIN
HTTPS: on # Assume HTTPS HTTPS: on # Assume HTTPS, adjust if site uses HTTP
# If using --allow-root, pass it here too, though it's also in WP_RUN_ARGS
# allow-root: true # Uncomment if needed, but should be covered by WP_RUN_ARGS
EOF EOF
# Set trap to clean up the temp file on exit # Ensure the config file is readable by the user executing WP-CLI
trap cleanup EXIT if [[ -n "$SUDO_CMD" ]]; then
# If using sudo -u, the target user needs to read it. Group readability might suffice.
chmod 640 "$WP_CLI_CONFIG_PATH" # Owner read/write, group read
# Attempt to chown group, ignore failure as permissions might be sufficient
chgrp "$WEB_GROUP" "$WP_CLI_CONFIG_PATH" || true
else
# Running as current user (root or web_user), user readability is enough
chmod 600 "$WP_CLI_CONFIG_PATH" # Owner read/write only
fi
# --- Database Setup --- # --- Database Setup ---
info "Setting up database..." info "Setting up database..."
@ -222,34 +298,53 @@ DB_PASSWORD=$(generate_password)
if [[ "$PERFORM_DB_ROOT_RESET" == "true" ]]; then if [[ "$PERFORM_DB_ROOT_RESET" == "true" ]]; then
# --- Risky Root Password Reset --- # --- Risky Root Password Reset ---
# This section requires the script runner to have root privileges or passwordless sudo for systemctl etc.
warning "Attempting to reset MariaDB/MySQL root password. This is risky!" warning "Attempting to reset MariaDB/MySQL root password. This is risky!"
new_root_password=$(generate_password) new_root_password=$(generate_password)
info "New root password will be: $new_root_password" info "New root password will be: $new_root_password"
info "Stopping MariaDB service..." DB_SERVICE_NAME="mariadb" # Common default, might need adjustment (e.g., mysql)
if ! sudo systemctl stop mariadb; then error_exit "Failed to stop MariaDB service."; fi info "Attempting to stop $DB_SERVICE_NAME service..."
# Wait a moment to ensure it stopped # Use sudo explicitly as service management requires root
if ! sudo systemctl stop "$DB_SERVICE_NAME"; then error_exit "Failed to stop $DB_SERVICE_NAME service. Check status/logs."; fi
info "$DB_SERVICE_NAME service stopped."
sleep 3 sleep 3
info "Starting MariaDB in safe mode (skip-grant-tables)..." info "Starting $DB_SERVICE_NAME in safe mode (skip-grant-tables)..."
sudo mysqld_safe --skip-grant-tables --skip-networking & # Find appropriate command
MYSQLD_SAFE_PID=$! MYSQLD_SAFE_CMD=""
# Wait for MariaDB to likely start in safe mode if command_exists mysqld_safe; then MYSQLD_SAFE_CMD="mysqld_safe";
sleep 10 # Increased wait time for reliability elif command_exists mariadbd-safe; then MYSQLD_SAFE_CMD="mariadbd-safe"; # Some distros use this
elif command_exists mariadbd; then MYSQLD_SAFE_CMD="mariadbd"; # Daemon directly might work
elif command_exists mysqld; then MYSQLD_SAFE_CMD="mysqld";
else error_exit "Cannot find mysqld_safe, mariadbd-safe, mariadbd, or mysqld command for safe mode."; fi
info "Attempting to reset root password..." # Use sudo explicitly for safe mode start
# Try multiple common root user variations sudo "$MYSQLD_SAFE_CMD" --skip-grant-tables --skip-networking &
if ! sudo mysql --protocol=socket -u root <<-EOF MYSQLD_SAFE_PID=$!
info "$MYSQLD_SAFE_CMD started in background (PID: $MYSQLD_SAFE_PID). Waiting for it to initialize..."
sleep 10 # Allow generous time for safe mode startup
info "Attempting to reset root password using mysql client..."
# Use sudo for mysql command connecting via socket as root
if ! sudo mysql --protocol=socket -u root <<-EOF &> /dev/null
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED BY '$new_root_password'; ALTER USER 'root'@'localhost' IDENTIFIED BY '$new_root_password';
FLUSH PRIVILEGES;
EXIT
EOF
then
warning "Failed 'ALTER USER' reset attempt (may be normal). Trying root@127.0.0.1..."
if ! sudo mysql --protocol=socket -u root <<-EOF &> /dev/null
FLUSH PRIVILEGES;
ALTER USER 'root'@'127.0.0.1' IDENTIFIED BY '$new_root_password'; ALTER USER 'root'@'127.0.0.1' IDENTIFIED BY '$new_root_password';
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
EXIT EXIT
EOF EOF
then then
warning "Failed initial root password reset attempt. Trying alternative command..." warning "Failed 'ALTER USER root@127.0.0.1' reset attempt. Trying legacy 'UPDATE' method..."
# Fallback for older MySQL/MariaDB versions # Fallback for older MySQL/MariaDB versions
if ! sudo mysql --protocol=socket -u root <<-EOF if ! sudo mysql --protocol=socket -u root <<-EOF &> /dev/null
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
UPDATE mysql.user SET Password=PASSWORD('$new_root_password') WHERE User='root' AND Host='localhost'; UPDATE mysql.user SET Password=PASSWORD('$new_root_password') WHERE User='root' AND Host='localhost';
UPDATE mysql.user SET Password=PASSWORD('$new_root_password') WHERE User='root' AND Host='127.0.0.1'; UPDATE mysql.user SET Password=PASSWORD('$new_root_password') WHERE User='root' AND Host='127.0.0.1';
@ -257,42 +352,46 @@ FLUSH PRIVILEGES;
EXIT EXIT
EOF EOF
then then
warning "Both root password reset methods failed. Trying grant table directly (less reliable)." warning "Failed legacy 'UPDATE' reset method. Trying direct 'authentication_string' update (MariaDB/newer MySQL)..."
# This might be needed on some very old or strangely configured systems if ! sudo mysql --protocol=socket -u root <<-EOF &> /dev/null
if ! sudo mysql --protocol=socket -u mysql <<-EOF
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
UPDATE mysql.user SET authentication_string=PASSWORD('$new_root_password') WHERE User='root'; UPDATE mysql.user SET authentication_string=PASSWORD('$new_root_password') WHERE User='root';
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
EXIT EXIT
EOF EOF
then then
error_exit "All attempts to reset the root password failed. Check MySQL/MariaDB logs. Manual intervention required." error_exit "All attempts to reset the root password in safe mode failed. Check MySQL/MariaDB logs. Manual intervention required."
fi
fi fi
fi fi
fi fi
success "Root password likely reset in safe mode." success "Root password likely reset in safe mode."
info "Stopping MariaDB safe mode process..." info "Stopping MariaDB safe mode process (PID: $MYSQLD_SAFE_PID)..."
# Attempt to kill the background mysqld_safe process gracefully first, then force if needed # Use sudo to kill processes started by root
sudo kill $MYSQLD_SAFE_PID || true # Try killing the specific PID sudo kill "$MYSQLD_SAFE_PID" || warning "Failed to kill specific PID $MYSQLD_SAFE_PID (maybe already stopped?)."
sleep 2 sleep 2
sudo pkill -f mysqld_safe || true # Broader attempt info "Attempting broader pkill for lingering safe mode processes..."
sudo pkill -f mysqld_safe || sudo pkill -f mariadbd-safe || true # Ignore errors if not found
sleep 2 sleep 2
sudo pkill -f mariadbd || sudo pkill -f mysqld || true # Kill any lingering daemons info "Attempting broader pkill for lingering database daemons..."
sudo pkill -f mariadbd || sudo pkill -f mysqld || true # Ignore errors if not found
sleep 3 sleep 3
success "Safe mode processes likely stopped."
info "Starting MariaDB service normally..." info "Starting $DB_SERVICE_NAME service normally..."
if ! sudo systemctl start mariadb; then error_exit "Failed to start MariaDB service after password reset."; fi if ! sudo systemctl start "$DB_SERVICE_NAME"; then error_exit "Failed to start $DB_SERVICE_NAME service after password reset. Check status/logs."; fi
sleep 5 # Allow time for service to initialize sleep 5 # Allow time for service to initialize
if ! sudo systemctl is-active --quiet mariadb; then error_exit "MariaDB service failed to start or become active. Check service status."; fi info "Verifying $DB_SERVICE_NAME service status..."
success "MariaDB service started successfully with new root password." if ! sudo systemctl is-active --quiet "$DB_SERVICE_NAME"; then error_exit "$DB_SERVICE_NAME service failed to start or become active. Check service status."; fi
DB_ROOT_PASS="$new_root_password" # Use the newly set password success "$DB_SERVICE_NAME service started successfully with new root password."
DB_ROOT_PASS="$new_root_password" # Use the newly set password for subsequent operations
else else
info "Using provided root password to create database and user." info "Using provided root password to create database and user."
# Test connection with provided root password # Test connection with provided root password
if ! mysql -u "$DB_ROOT_USER" -p"$DB_ROOT_PASS" -h "$DB_HOST" -e "SELECT 1;" &> /dev/null; then if ! mysql -u "$DB_ROOT_USER" -p"$DB_ROOT_PASS" -h "$DB_HOST" -e "SELECT 1;" &> /dev/null; then
error_exit "Failed to connect to database using provided root credentials. Check user, password, and host." error_exit "Failed to connect to database using provided root credentials. Check user '$DB_ROOT_USER', password, and host '$DB_HOST'."
fi fi
success "Database root connection successful." success "Database root connection successful."
fi fi
@ -300,141 +399,125 @@ fi
# --- Create WordPress Database and User --- # --- Create WordPress Database and User ---
info "Creating WordPress database '$DB_NAME' and user '$DB_USER'..." info "Creating WordPress database '$DB_NAME' and user '$DB_USER'..."
# Use printf for safer password injection into the command # Use printf for safer password injection into the command
SQL_COMMAND=$(printf "CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER IF NOT EXISTS '%s'@'%s' IDENTIFIED BY '%s'; GRANT ALL PRIVILEGES ON %s.* TO '%s'@'%s'; FLUSH PRIVILEGES;" \ SQL_COMMAND=$(printf "CREATE DATABASE IF NOT EXISTS \`%s\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER IF NOT EXISTS '%s'@'%s' IDENTIFIED BY '%s'; GRANT ALL PRIVILEGES ON \`%s\`.* TO '%s'@'%s'; FLUSH PRIVILEGES;" \
"$DB_NAME" "$DB_USER" "$DB_HOST" "$DB_PASSWORD" "$DB_NAME" "$DB_USER" "$DB_HOST") "$DB_NAME" "$DB_USER" "$DB_HOST" "$DB_PASSWORD" "$DB_NAME" "$DB_USER" "$DB_HOST")
# Use the DB_ROOT_PASS (either provided or newly generated)
if ! mysql -u "$DB_ROOT_USER" -p"$DB_ROOT_PASS" -h "$DB_HOST" -e "$SQL_COMMAND"; then if ! mysql -u "$DB_ROOT_USER" -p"$DB_ROOT_PASS" -h "$DB_HOST" -e "$SQL_COMMAND"; then
error_exit "Failed to create WordPress database or user. Check MySQL/MariaDB logs and permissions." error_exit "Failed to execute SQL command to create WordPress database/user. Check MySQL/MariaDB logs and permissions for '$DB_ROOT_USER'."
fi fi
success "Database and user created successfully." success "Database '$DB_NAME' and user '$DB_USER' created successfully."
# --- WordPress Installation --- # --- WordPress Core File Setup ---
info "Navigating to WordPress root: $WP_ROOT" info "Ensuring WordPress files are present in: $WP_ROOT"
cd "$WP_ROOT" || error_exit "Failed to change directory to $WP_ROOT" cd "$WP_ROOT" || error_exit "Failed to change directory to $WP_ROOT"
# Backup existing wp-config.php if it exists # Backup existing wp-config.php if it exists
if [[ -f "wp-config.php" ]]; then if [[ -f "wp-config.php" ]]; then
BACKUP_NAME="wp-config.php.bak.$(date +%Y%m%d%H%M%S)" BACKUP_NAME="wp-config.php.bak.$(date +%Y%m%d_%H%M%S)" # Use underscore in timestamp
info "Backing up existing wp-config.php to $BACKUP_NAME" info "Backing up existing wp-config.php to $BACKUP_NAME"
cp wp-config.php "$BACKUP_NAME" cp wp-config.php "$BACKUP_NAME" || warning "Failed to backup wp-config.php"
fi fi
# Download WordPress core files if necessary (index.php is a good indicator) # Download WordPress core files ONLY if key files/dirs are missing
# Run wp commands as the web user if possible, falling back to --allow-root if needed if [[ ! -f "index.php" || ! -f "wp-includes/version.php" || ! -d "wp-admin" ]]; then
WP_RUN_ARGS=("--path=$WP_ROOT") info "WordPress core files seem missing or incomplete. Downloading..."
# Determine if sudo is needed to run as web user # Use determined $SUDO_CMD, $WP_EXECUTABLE, and $WP_RUN_ARGS
SUDO_CMD="" if ! $SUDO_CMD $WP_EXECUTABLE core download "${WP_RUN_ARGS[@]}" --skip-content --version=latest; then
if [[ "$(id -u)" -ne "$(id -u "$WEB_USER")" ]]; then error_exit "Failed to download WordPress core files using WP-CLI."
SUDO_CMD="sudo -u $WEB_USER"
# Check if we can sudo without password, otherwise need --allow-root
if ! sudo -n -u "$WEB_USER" true &>/dev/null; then
warning "Cannot sudo to '$WEB_USER' without a password. Using --allow-root for WP-CLI commands."
SUDO_CMD="" # Clear sudo command
WP_RUN_ARGS+=("--allow-root")
else
info "Running WP-CLI commands as user '$WEB_USER'."
fi
fi
if [[ ! -f "index.php" || ! -d "wp-admin" ]]; then
info "WordPress core files not found. Downloading..."
if ! $SUDO_CMD wp core download "${WP_RUN_ARGS[@]}" --skip-content --version=latest; then
error_exit "Failed to download WordPress core files."
fi fi
success "WordPress core downloaded." success "WordPress core downloaded."
else else
info "WordPress files already exist. Skipping download." info "WordPress core files seem to exist. Skipping download."
fi fi
# --- Create wp-config.php --- # --- Create wp-config.php ---
# Using wp cli command is generally preferred, but manual creation is more robust if WP-CLI has issues.
# We'll stick to the manual creation from your original script as it included specific fixes.
info "Creating wp-config.php..." info "Creating wp-config.php..."
# Generate Salts using WP-CLI if possible, otherwise fallback to openssl # Generate Salts using WP-CLI
SALTS=$($SUDO_CMD wp config salt generate --raw "${WP_RUN_ARGS[@]}" 2>/dev/null || { info "Generating WordPress salts using WP-CLI..."
warning "Could not generate salts using WP-CLI, falling back to openssl (less standard format)." # Use determined $SUDO_CMD, $WP_EXECUTABLE, and $WP_RUN_ARGS
echo "define( 'AUTH_KEY', '$(generate_password)' );" SALTS=$($SUDO_CMD $WP_EXECUTABLE config salt generate --raw "${WP_RUN_ARGS[@]}" 2>/dev/null) || {
echo "define( 'SECURE_AUTH_KEY', '$(generate_password)' );" warning "Could not generate salts using WP-CLI. Falling back to openssl (less standard format)."
echo "define( 'LOGGED_IN_KEY', '$(generate_password)' );" SALTS=$(cat <<EOF
echo "define( 'NONCE_KEY', '$(generate_password)' );" define( 'AUTH_KEY', '$(generate_password)' );
echo "define( 'AUTH_SALT', '$(generate_password)' );" define( 'SECURE_AUTH_KEY', '$(generate_password)' );
echo "define( 'SECURE_AUTH_SALT', '$(generate_password)' );" define( 'LOGGED_IN_KEY', '$(generate_password)' );
echo "define( 'LOGGED_IN_SALT', '$(generate_password)' );" define( 'NONCE_KEY', '$(generate_password)' );
echo "define( 'NONCE_SALT', '$(generate_password)' );" define( 'AUTH_SALT', '$(generate_password)' );
}) define( 'SECURE_AUTH_SALT', '$(generate_password)' );
define( 'LOGGED_IN_SALT', '$(generate_password)' );
define( 'NONCE_SALT', '$(generate_password)' );
EOF
)
}
# Check if salts were actually generated
if [[ -z "$SALTS" ]]; then
error_exit "Failed to generate WordPress salts using both WP-CLI and fallback."
fi
success "WordPress salts generated."
# Use cat with heredoc for wp-config.php creation # Use cat with heredoc for wp-config.php creation
cat > wp-config.php <<EOF cat > wp-config.php <<EOF || error_exit "Failed to write wp-config.php file."
<?php <?php
/** /**
* The base configuration for WordPress * The base configuration for WordPress
*
* @link https://wordpress.org/support/article/editing-wp-config-php/ * @link https://wordpress.org/support/article/editing-wp-config-php/
*
* @package WordPress * @package WordPress
*/ */
// ** Database settings - You can get this info from your web host ** // // ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */ /** The name of the database for WordPress */
define( 'DB_NAME', '${DB_NAME}' ); define( 'DB_NAME', '${DB_NAME}' );
/** Database username */ /** Database username */
define( 'DB_USER', '${DB_USER}' ); define( 'DB_USER', '${DB_USER}' );
/** Database password */ /** Database password */
define( 'DB_PASSWORD', '${DB_PASSWORD}' ); define( 'DB_PASSWORD', '${DB_PASSWORD}' );
/** Database hostname */ /** Database hostname */
define( 'DB_HOST', '${DB_HOST}' ); define( 'DB_HOST', '${DB_HOST}' );
/** Database charset to use in creating database tables. */ /** Database charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8mb4' ); // Use utf8mb4 for better character support define( 'DB_CHARSET', 'utf8mb4' );
/** The database collate type. Don't change this if in doubt. */ /** The database collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', '' ); define( 'DB_COLLATE', '' );
/**#@+ /**#@+
* Authentication unique keys and salts. * Authentication unique keys and salts.
* Generate these at: https://api.wordpress.org/secret-key/1.1/salt/ * @since 2.6.0
* You can change these at any point to invalidate all existing cookies. This will force all users to have to log in again.
*/ */
${SALTS} ${SALTS}
/**#@-*/ /**#@-*/
/** /**
* WordPress database table prefix. * WordPress database table prefix.
* You can have multiple installations in one database if you give each
* a unique prefix. Only numbers, letters, and underscores please!
*/ */
\$table_prefix = 'wp_'; \$table_prefix = 'wp_'; // Default prefix
/** /**
* For developers: WordPress debugging mode. * For developers: WordPress debugging mode.
* Change this to true to enable the display of notices during development.
* It is strongly recommended that plugin and theme developers use WP_DEBUG
* in their development environments.
* For information on other constants that can be used for debugging,
* visit the documentation.
* @link https://wordpress.org/support/article/debugging-in-wordpress/
*/ */
define( 'WP_DEBUG', false ); define( 'WP_DEBUG', false ); // Set to true for development
/* Add any custom values between this line and the "stop editing" line. */ /* Add any custom values between this line and the "stop editing" line. */
// Memory Limits
define( 'WP_MEMORY_LIMIT', '256M' ); define( 'WP_MEMORY_LIMIT', '256M' );
define( 'WP_AUTO_UPDATE_CORE', false ); // Or 'minor' or true define( 'WP_MAX_MEMORY_LIMIT', '512M' ); // For admin area
// Updates
define( 'WP_AUTO_UPDATE_CORE', false ); // Recommended: 'minor' or true for security
// File Editing
define( 'DISALLOW_FILE_EDIT', true ); // Security enhancement
// Define site URL and home URL (ensure protocol matches server setup) // Define site URL and home URL (ensure protocol matches server setup)
define( 'WP_HOME', 'https://${DOMAIN}' ); define( 'WP_HOME', 'https://${DOMAIN}' );
define( 'WP_SITEURL', 'https://${DOMAIN}' ); define( 'WP_SITEURL', 'https://${DOMAIN}' );
// If using a reverse proxy, this might be needed: // If using a reverse proxy handling SSL, uncomment the following:
// if (isset(\$_SERVER['HTTP_X_FORWARDED_PROTO']) && \$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { // if (isset(\$_SERVER['HTTP_X_FORWARDED_PROTO']) && \$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
// \$_SERVER['HTTPS'] = 'on'; // \$_SERVER['HTTPS'] = 'on';
// } // }
// Fix missing HTTP_HOST for CLI operations // Fix missing HTTP_HOST for CLI operations (redundant if using WP_CLI_CONFIG_PATH, but safe)
if ( defined( 'WP_CLI' ) && WP_CLI && ! isset( \$_SERVER['HTTP_HOST'] ) ) { if ( defined( 'WP_CLI' ) && WP_CLI && ! isset( \$_SERVER['HTTP_HOST'] ) ) {
\$_SERVER['HTTP_HOST'] = '${DOMAIN}'; \$_SERVER['HTTP_HOST'] = '${DOMAIN}';
} }
@ -451,61 +534,69 @@ if ( ! defined( 'ABSPATH' ) ) {
require_once ABSPATH . 'wp-settings.php'; require_once ABSPATH . 'wp-settings.php';
EOF EOF
if [[ ! -f "wp-config.php" ]]; then
error_exit "Failed to create wp-config.php file."
fi
success "wp-config.php created successfully." success "wp-config.php created successfully."
# --- Set Permissions --- # --- Set Permissions ---
info "Setting file permissions..." info "Setting file permissions for $WP_ROOT..."
# Set ownership first # Use sudo explicitly as permissions/ownership changes usually require root
# Set ownership first - recursively
info "Setting ownership to ${WEB_USER}:${WEB_GROUP}..."
if ! sudo chown -R "${WEB_USER}:${WEB_GROUP}" "$WP_ROOT"; then if ! sudo chown -R "${WEB_USER}:${WEB_GROUP}" "$WP_ROOT"; then
error_exit "Failed to set ownership on $WP_ROOT." # Don't exit, maybe some files failed, but core install might still work
warning "Failed to set ownership on some files/dirs within $WP_ROOT. Check permissions."
fi fi
# Set directory and file permissions # Set directory and file permissions
# Ensure the script runner has write access to the directory to run find info "Setting directory permissions to 755 and file permissions to 644..."
cd "$WP_ROOT" || error_exit "Failed to cd into $WP_ROOT before setting permissions" # Use sudo find. Requires runner (root) to have read access everywhere.
if ! sudo find . -type d -exec chmod 755 {} \; ; then if ! sudo find "$WP_ROOT" -type d -exec chmod 755 {} \; ; then
warning "Could not set directory permissions using find. Check sudo permissions." warning "Could not set directory permissions using find. Check permissions."
fi fi
if ! sudo find . -type f -exec chmod 644 {} \; ; then if ! sudo find "$WP_ROOT" -type f -exec chmod 644 {} \; ; then
warning "Could not set file permissions using find. Check sudo permissions." warning "Could not set file permissions using find. Check permissions."
fi fi
# Ensure wp-config.php is readable by the web server, but not world-writable # Ensure wp-config.php is more secure (readable only by owner and group)
if ! sudo chmod 640 wp-config.php; then # More secure permission info "Setting specific permissions for wp-config.php to 640..."
warning "Could not set specific permissions on wp-config.php." if [[ -f "wp-config.php" ]]; then
if ! sudo chmod 640 wp-config.php; then
warning "Could not set specific permissions (640) on wp-config.php."
fi
else
warning "wp-config.php not found during permission setting phase."
fi fi
success "File permissions set." success "File permissions set (potential warnings noted)."
# --- WordPress Core Installation --- # --- WordPress Core Installation ---
# Check if WordPress is already installed before attempting installation info "Checking if WordPress is already installed..."
if ! $SUDO_CMD wp core is-installed "${WP_RUN_ARGS[@]}"; then # Use determined $SUDO_CMD, $WP_EXECUTABLE, and $WP_RUN_ARGS
if ! $SUDO_CMD $WP_EXECUTABLE core is-installed "${WP_RUN_ARGS[@]}"; then
info "WordPress is not installed. Proceeding with installation..." info "WordPress is not installed. Proceeding with installation..."
if ! $SUDO_CMD wp core install \ # Use determined $SUDO_CMD, $WP_EXECUTABLE, and $WP_RUN_ARGS
--url="https://$DOMAIN" \ if ! $SUDO_CMD $WP_EXECUTABLE core install \
--url="https://${DOMAIN}" \
--title="My WordPress Site on $DOMAIN" \ --title="My WordPress Site on $DOMAIN" \
--admin_user="$WP_ADMIN_USER" \ --admin_user="$WP_ADMIN_USER" \
--admin_password="$WP_ADMIN_PASS" \ --admin_password="$WP_ADMIN_PASS" \
--admin_email="$WP_ADMIN_EMAIL" \ --admin_email="$WP_ADMIN_EMAIL" \
--skip-email \ --skip-email \
"${WP_RUN_ARGS[@]}"; then # Added skip-email "${WP_RUN_ARGS[@]}"; then
error_exit "WordPress core installation failed." error_exit "WordPress core installation failed using WP-CLI."
fi fi
info "Removing default plugins (Akismet, Hello Dolly)..." info "Removing default plugins (Akismet, Hello Dolly)..."
$SUDO_CMD wp plugin delete akismet hello "${WP_RUN_ARGS[@]}" --quiet || warning "Could not delete default plugins." # Use determined $SUDO_CMD, $WP_EXECUTABLE, and $WP_RUN_ARGS
$SUDO_CMD $WP_EXECUTABLE plugin delete akismet hello "${WP_RUN_ARGS[@]}" --quiet || warning "Could not delete default plugins (might not exist)."
success "WordPress installed successfully." success "WordPress installed successfully via WP-CLI."
else else
info "WordPress is already installed." info "WordPress is already installed according to WP-CLI."
# Optionally update URL if needed (be careful with this) # Optionally update URL if needed (be careful with this, can break site if proxy/etc involved)
# info "Verifying site URL..." # info "Verifying site URL options..."
# $SUDO_CMD wp option update siteurl "https://$DOMAIN" "${WP_RUN_ARGS[@]}" # $SUDO_CMD $WP_EXECUTABLE option update siteurl "https://$DOMAIN" "${WP_RUN_ARGS[@]}" || warning "Failed to update siteurl option"
# $SUDO_CMD wp option update home "https://$DOMAIN" "${WP_RUN_ARGS[@]}" # $SUDO_CMD $WP_EXECUTABLE option update home "https://$DOMAIN" "${WP_RUN_ARGS[@]}" || warning "Failed to update home option"
fi fi
# --- Final Summary --- # --- Final Summary ---
success "WordPress setup completed!" success "WordPress setup process completed!"
printf "\n--- ${YELLOW}Installation Summary${NC} ---\n" printf "\n--- ${YELLOW}Installation Summary${NC} ---\n"
printf "Site URL: ${GREEN}https://%s${NC}\n" "$DOMAIN" printf "Site URL: ${GREEN}https://%s${NC}\n" "$DOMAIN"
printf "WP Root: ${GREEN}%s${NC}\n" "$WP_ROOT" printf "WP Root: ${GREEN}%s${NC}\n" "$WP_ROOT"
@ -523,9 +614,12 @@ printf " Username: ${GREEN}%s${NC}\n" "$DB_USER"
printf " Password: ${YELLOW}%s${NC} (Keep this safe!)\n" "$DB_PASSWORD" printf " Password: ${YELLOW}%s${NC} (Keep this safe!)\n" "$DB_PASSWORD"
printf " Host: ${GREEN}%s${NC}\n" "$DB_HOST" printf " Host: ${GREEN}%s${NC}\n" "$DB_HOST"
if [[ "$PERFORM_DB_ROOT_RESET" == "true" ]]; then if [[ "$PERFORM_DB_ROOT_RESET" == "true" ]]; then
printf "\n${RED}IMPORTANT: The MySQL/MariaDB root password was reset to:${NC}\n" printf "\n${RED}IMPORTANT: The MySQL/MariaDB root password was reset during this process.${NC}\n"
printf " Root Password: ${YELLOW}%s${NC}\n" "$DB_ROOT_PASS" printf " New Root Pass: ${YELLOW}%s${NC} (Keep this safe!)\n" "$DB_ROOT_PASS"
fi fi
printf "---------------------------\n" printf "---------------------------\n"
# Explicitly call cleanup before final exit (trap should also handle it)
cleanup
exit 0 exit 0