2025-03-31 17:37:54 +00:00
#!/bin/bash
2025-04-04 17:10:15 +00:00
#
# Script to automate WordPress installation.
# Includes dependency checks, WP-CLI installation, database setup,
# WordPress core installation, and configuration.
2025-04-04 17:23:26 +00:00
# v2.1 - Fixes WP-CLI execution path issues when run as root.
2025-04-04 17:10:15 +00:00
#
2025-03-31 17:37:54 +00:00
2025-04-04 17:10:15 +00:00
# --- Configuration ---
# Exit immediately if a command exits with a non-zero status.
2025-03-31 17:37:54 +00:00
set -e
2025-04-04 17:10:15 +00:00
# Treat unset variables as an error when substituting.
set -u
# Pipe commands return the exit status of the last command in the pipe
set -o pipefail
2025-03-31 17:37:54 +00:00
2025-04-04 17:10:15 +00:00
# Colors for output messages
2025-03-31 17:37:54 +00:00
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
2025-04-04 17:10:15 +00:00
BLUE = '\033[0;34m'
2025-03-31 17:37:54 +00:00
NC = '\033[0m' # No Color
2025-04-04 17:10:15 +00:00
# --- Default Values ---
WP_ADMIN_USER = ""
WP_ADMIN_PASS = ""
WP_ADMIN_EMAIL = ""
WP_ROOT = "/var/www/webroot/ROOT" # Default WordPress root directory
DOMAIN = "" # Domain will be determined or required
DB_HOST = "127.0.0.1"
DB_ROOT_USER = "root"
DB_ROOT_PASS = "" # Require user to provide this for security
2025-04-04 17:23:26 +00:00
# PERFORM_DB_ROOT_RESET is set during arg parsing
WEB_USER = "litespeed" # Web server user
2025-04-04 17:10:15 +00:00
WEB_GROUP = "litespeed" # Web server group
2025-04-04 17:23:26 +00:00
WP_CLI_PATH = "/usr/local/bin/wp" # Path to WP-CLI executable
2025-04-04 17:10:15 +00:00
# --- Helper Functions ---
# Print informational messages
info( ) {
printf " ${ BLUE } [INFO] %s ${ NC } \n " " $@ "
}
# Print success messages
success( ) {
printf " ${ GREEN } [SUCCESS] %s ${ NC } \n " " $@ "
}
2025-03-31 17:37:54 +00:00
2025-04-04 17:10:15 +00:00
# Print warning messages
warning( ) {
printf " ${ YELLOW } [WARNING] %s ${ NC } \n " " $@ "
}
# Print error messages and exit
error_exit( ) {
printf " ${ RED } [ERROR] %s ${ NC } \n " " $@ " >& 2
2025-04-04 17:23:26 +00:00
# Attempt cleanup before exiting
cleanup & > /dev/null || true
2025-04-04 17:10:15 +00:00
exit 1
}
# Function to display usage information
2025-03-31 17:37:54 +00:00
usage( ) {
2025-04-04 17:10:15 +00:00
printf "Usage: %s [OPTIONS]\n" " $0 "
printf "\n"
printf "Automates the installation of WordPress.\n"
printf "\n"
printf "Required Options:\n"
printf " --wpusername=USERNAME WordPress admin username (mandatory)\n"
printf " --wppassword=PASSWORD WordPress admin password (mandatory)\n"
printf " --wpemail=EMAIL WordPress admin email (mandatory)\n"
2025-04-04 17:23:26 +00:00
# Make dbrootpass conditional in description
printf " --dbrootpass=PASSWORD Current MySQL/MariaDB root password (required IF NOT using --reset-db-root-pass)\n"
2025-04-04 17:10:15 +00:00
printf "\n"
printf "Optional Options:\n"
printf " --wproot=PATH WordPress installation directory (default: %s)\n" " $WP_ROOT "
printf " --domain=DOMAIN Domain name for the site (default: auto-detected from hostname)\n"
printf " --webuser=USER Web server user (default: %s)\n" " $WEB_USER "
printf " --webgroup=GROUP Web server group (default: %s)\n" " $WEB_GROUP "
printf " --dbhost=HOST Database host (default: %s)\n" " $DB_HOST "
2025-04-04 17:23:26 +00:00
printf " --reset-db-root-pass Perform the risky root password reset (requires script runner with root privileges)\n"
2025-04-04 17:10:15 +00:00
printf " -h, --help Display this help message\n"
printf "\n"
printf "Example:\n"
printf " %s --wpusername=myuser --wppassword='securePass' --wpemail=me@example.com --dbrootpass='currentRootPass'\n" " $0 "
printf " %s --wpusername=myuser --wppassword='securePass' --wpemail=me@example.com --reset-db-root-pass --domain=example.com\n" " $0 "
2025-03-31 17:37:54 +00:00
exit 1
}
2025-04-04 17:10:15 +00:00
# Function to check if a command exists
command_exists( ) {
command -v " $1 " & > /dev/null
}
2025-03-31 17:37:54 +00:00
2025-04-04 17:10:15 +00:00
# Function to generate a random secure password
generate_password( ) {
2025-04-04 17:23:26 +00:00
openssl rand -base64 16
2025-04-04 17:10:15 +00:00
}
2025-03-31 17:37:54 +00:00
2025-04-04 17:10:15 +00:00
# Function to clean up temporary files
2025-04-04 17:23:26 +00:00
# Define WP_CLI_CONFIG_PATH early so cleanup function knows about it
WP_CLI_CONFIG_PATH = " /tmp/wp-cli-config- $RANDOM .yml "
2025-04-04 17:10:15 +00:00
cleanup( ) {
2025-04-04 17:23:26 +00:00
# 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 "
fi
2025-04-04 17:10:15 +00:00
}
2025-03-31 17:37:54 +00:00
2025-04-04 17:10:15 +00:00
# --- Argument Parsing ---
TEMP = $( getopt -o h --longoptions help,wpusername:,wppassword:,wpemail:,wproot:,domain:,dbhost:,dbrootpass:,reset-db-root-pass,webuser:,webgroup: -n " $0 " -- " $@ " )
if [ $? != 0 ] ; then
2025-04-04 17:23:26 +00:00
error_exit "Terminating... Invalid arguments provided."
2025-03-31 17:37:54 +00:00
fi
2025-04-04 17:10:15 +00:00
eval set -- " $TEMP "
unset TEMP
2025-03-31 17:37:54 +00:00
2025-04-04 17:23:26 +00:00
PERFORM_DB_ROOT_RESET = "false" # Default value
2025-03-31 17:37:54 +00:00
2025-04-04 17:10:15 +00:00
while true; do
case " $1 " in
--wpusername) WP_ADMIN_USER = " $2 " ; shift 2 ; ;
--wppassword) WP_ADMIN_PASS = " $2 " ; shift 2 ; ;
--wpemail) WP_ADMIN_EMAIL = " $2 " ; shift 2 ; ;
--wproot) WP_ROOT = " $2 " ; shift 2 ; ;
--domain) DOMAIN = " $2 " ; shift 2 ; ;
--dbhost) DB_HOST = " $2 " ; shift 2 ; ;
--dbrootpass) DB_ROOT_PASS = " $2 " ; shift 2 ; ;
--reset-db-root-pass) PERFORM_DB_ROOT_RESET = "true" ; shift 1 ; ;
--webuser) WEB_USER = " $2 " ; shift 2 ; ;
--webgroup) WEB_GROUP = " $2 " ; shift 2 ; ;
-h| --help) usage ; ;
2025-04-04 17:23:26 +00:00
--) shift ; break ; ;
*) error_exit " Internal error parsing options! Unexpected option: $1 " ; ;
2025-04-04 17:10:15 +00:00
esac
done
2025-03-31 17:37:54 +00:00
2025-04-04 17:23:26 +00:00
# Set trap *after* WP_CLI_CONFIG_PATH is defined and potentially used
trap cleanup EXIT SIGINT SIGTERM
2025-04-04 17:10:15 +00:00
# --- Validation ---
info "Validating parameters..."
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_EMAIL " ] ] ; then error_exit "WordPress admin email (--wpemail) is required." ; fi
2025-04-04 17:23:26 +00:00
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
2025-04-04 17:10:15 +00:00
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
2025-04-04 17:23:26 +00:00
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
2025-04-04 17:10:15 +00:00
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 ! getent group " $WEB_GROUP " & >/dev/null; then error_exit " Web group ' $WEB_GROUP ' does not exist. " ; fi
2025-04-04 17:23:26 +00:00
success "Parameters validated."
2025-04-04 17:10:15 +00:00
# --- Determine Domain ---
if [ [ -z " $DOMAIN " ] ] ; then
2025-04-04 17:23:26 +00:00
if ! command_exists hostname; then error_exit "'hostname' command not found. Please install it or specify --domain." ; fi
2025-04-04 17:10:15 +00:00
FULL_HOSTNAME = $( hostname -f)
2025-04-04 17:23:26 +00:00
DOMAIN = $( echo " $FULL_HOSTNAME " | sed -E 's/^(node[0-9]*-|wp-|web-|host-|localhost)//' ) # Slightly more aggressive cleaning
if [ [ -z " $DOMAIN " || " $DOMAIN " = = " $FULL_HOSTNAME " || " $DOMAIN " = = "localdomain" ] ] ; then # Handle cases where sed didn't change much
warning " Could not reliably determine a public domain from hostname ' $FULL_HOSTNAME '. Using it as is. "
2025-04-04 17:10:15 +00:00
DOMAIN = " $FULL_HOSTNAME "
2025-04-04 17:23:26 +00:00
# Consider erroring out if domain detection is critical and fails
# error_exit "Failed to determine domain automatically. Please specify using --domain."
2025-04-04 17:10:15 +00:00
fi
2025-04-04 17:23:26 +00:00
info " Auto-detected domain: $DOMAIN (Use --domain to override if incorrect) "
2025-04-04 17:10:15 +00:00
else
info " Using specified domain: $DOMAIN "
2025-03-31 17:37:54 +00:00
fi
2025-04-04 17:10:15 +00:00
# --- Dependency Checks ---
info "Checking dependencies..."
2025-04-04 17:23:26 +00:00
# Group related checks
if ! command_exists php; then error_exit "'php' command not found. Please install PHP." ; fi
if ! command_exists mysql; then error_exit "'mysql' command (client) not found. Please install MySQL/MariaDB client." ; fi
if ! command_exists curl; then error_exit "'curl' command not found. Please install curl." ; fi
if ! command_exists openssl; then error_exit "'openssl' command not found. Please install openssl." ; fi
if ! command_exists getopt; then error_exit "'getopt' command not found. Please install getopt." ; fi
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
2025-04-04 17:10:15 +00:00
if [ [ " $PERFORM_DB_ROOT_RESET " = = "true" ] ] ; then
2025-04-04 17:23:26 +00:00
if ! command_exists systemctl; then error_exit "'systemctl' command not found, but required for --reset-db-root-pass." ; fi
2025-04-04 17:10:15 +00:00
if ! command_exists pkill; then error_exit "'pkill' command not found, but required for --reset-db-root-pass." ; fi
2025-04-04 17:23:26 +00:00
# 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
2025-03-31 17:37:54 +00:00
fi
2025-04-04 17:23:26 +00:00
success "All essential dependencies found."
2025-03-31 17:37:54 +00:00
2025-04-04 17:10:15 +00:00
# --- WP-CLI Setup ---
2025-04-04 17:23:26 +00:00
# Check if WP-CLI executable exists at the defined path
if [ [ ! -x " $WP_CLI_PATH " ] ] ; then
info " WP-CLI not found at $WP_CLI_PATH or not executable. Attempting installation... "
TEMP_WP_CLI = "./wp-cli.phar"
if ! curl -o " $TEMP_WP_CLI " https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar; then
2025-04-04 17:10:15 +00:00
error_exit "Failed to download WP-CLI."
fi
2025-04-04 17:23:26 +00:00
chmod +x " $TEMP_WP_CLI "
# Attempt to move using sudo. This requires sudo privileges.
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. "
2025-04-04 17:10:15 +00:00
fi
2025-04-04 17:23:26 +00:00
# Verify installation by checking the target path again
if [ [ ! -x " $WP_CLI_PATH " ] ] ; then
error_exit " WP-CLI installation failed unexpectedly. $WP_CLI_PATH not found or not executable after move. "
2025-03-31 17:37:54 +00:00
fi
2025-04-04 17:10:15 +00:00
success " WP-CLI installed successfully to $WP_CLI_PATH "
2025-03-31 17:37:54 +00:00
else
2025-04-04 17:23:26 +00:00
success " WP-CLI is already installed at $WP_CLI_PATH . "
2025-03-31 17:37:54 +00:00
fi
2025-04-04 17:23:26 +00:00
# --- WP-CLI Execution Context Setup ---
# Determine how WP-CLI commands should be run (user, flags)
# This logic prioritizes running as root if available, to avoid sudo PATH issues.
WP_RUN_ARGS = ( " --path= $WP_ROOT " )
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.
2025-04-04 17:10:15 +00:00
apache_modules:
- mod_rewrite
_:
server:
HTTP_HOST: $DOMAIN
2025-04-04 17:23:26 +00:00
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
2025-04-04 17:10:15 +00:00
EOF
2025-04-04 17:23:26 +00:00
# Ensure the config file is readable by the user executing WP-CLI
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
2025-04-04 17:10:15 +00:00
# --- Database Setup ---
info "Setting up database..."
DB_NAME = " wpdb_ $( openssl rand -hex 4) "
DB_USER = " wpuser_ $( openssl rand -hex 4) "
DB_PASSWORD = $( generate_password)
if [ [ " $PERFORM_DB_ROOT_RESET " = = "true" ] ] ; then
# --- Risky Root Password Reset ---
2025-04-04 17:23:26 +00:00
# This section requires the script runner to have root privileges or passwordless sudo for systemctl etc.
2025-04-04 17:10:15 +00:00
warning "Attempting to reset MariaDB/MySQL root password. This is risky!"
new_root_password = $( generate_password)
info " New root password will be: $new_root_password "
2025-04-04 17:23:26 +00:00
DB_SERVICE_NAME = "mariadb" # Common default, might need adjustment (e.g., mysql)
info " Attempting to stop $DB_SERVICE_NAME service... "
# 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. "
2025-04-04 17:10:15 +00:00
sleep 3
2025-04-04 17:23:26 +00:00
info " Starting $DB_SERVICE_NAME in safe mode (skip-grant-tables)... "
# Find appropriate command
MYSQLD_SAFE_CMD = ""
if command_exists mysqld_safe; then MYSQLD_SAFE_CMD = "mysqld_safe" ;
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
# Use sudo explicitly for safe mode start
sudo " $MYSQLD_SAFE_CMD " --skip-grant-tables --skip-networking &
2025-04-04 17:10:15 +00:00
MYSQLD_SAFE_PID = $!
2025-04-04 17:23:26 +00:00
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
2025-04-04 17:10:15 +00:00
2025-04-04 17:27:20 +00:00
# Generate a simpler password without special characters to avoid auth issues
new_root_password = $( openssl rand -base64 12 | tr -dc 'a-zA-Z0-9' | head -c 16)
info " Using simplified password format: $new_root_password "
2025-04-04 17:23:26 +00:00
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
2025-03-31 17:37:54 +00:00
FLUSH PRIVILEGES;
ALTER USER 'root' @'localhost' IDENTIFIED BY '$new_root_password' ;
2025-04-04 17:27:20 +00:00
ALTER USER 'root' @'127.0.0.1' IDENTIFIED BY '$new_root_password' ;
2025-03-31 17:37:54 +00:00
FLUSH PRIVILEGES;
2025-04-04 17:10:15 +00:00
EXIT
2025-03-31 17:37:54 +00:00
EOF
2025-04-04 17:10:15 +00:00
then
2025-04-04 17:27:20 +00:00
warning "Failed 'ALTER USER' reset attempt (may be normal). Trying alternative syntax..."
# Try the mysql_native_password plugin explicitly
2025-04-04 17:23:26 +00:00
if ! sudo mysql --protocol= socket -u root <<-EOF & > /dev/null
FLUSH PRIVILEGES;
2025-04-04 17:27:20 +00:00
ALTER USER 'root' @'localhost' IDENTIFIED WITH mysql_native_password BY '$new_root_password' ;
ALTER USER 'root' @'127.0.0.1' IDENTIFIED WITH mysql_native_password BY '$new_root_password' ;
2025-04-04 17:23:26 +00:00
FLUSH PRIVILEGES;
EXIT
EOF
then
2025-04-04 17:27:20 +00:00
warning "Failed with native password plugin. Trying legacy 'UPDATE' method..."
2025-04-04 17:23:26 +00:00
# Fallback for older MySQL/MariaDB versions
if ! sudo mysql --protocol= socket -u root <<-EOF & > /dev/null
2025-04-04 17:10:15 +00:00
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 = '127.0.0.1' ;
FLUSH PRIVILEGES;
EXIT
EOF
2025-04-04 17:23:26 +00:00
then
warning "Failed legacy 'UPDATE' reset method. Trying direct 'authentication_string' update (MariaDB/newer MySQL)..."
if ! sudo mysql --protocol= socket -u root <<-EOF & > /dev/null
2025-04-04 17:10:15 +00:00
FLUSH PRIVILEGES;
UPDATE mysql.user SET authentication_string = PASSWORD( '$new_root_password' ) WHERE User = 'root' ;
FLUSH PRIVILEGES;
EXIT
EOF
2025-04-04 17:23:26 +00:00
then
error_exit "All attempts to reset the root password in safe mode failed. Check MySQL/MariaDB logs. Manual intervention required."
fi
fi
2025-04-04 17:10:15 +00:00
fi
fi
success "Root password likely reset in safe mode."
2025-04-04 17:23:26 +00:00
info " Stopping MariaDB safe mode process (PID: $MYSQLD_SAFE_PID )... "
# Use sudo to kill processes started by root
sudo kill " $MYSQLD_SAFE_PID " || warning " Failed to kill specific PID $MYSQLD_SAFE_PID (maybe already stopped?). "
2025-04-04 17:10:15 +00:00
sleep 2
2025-04-04 17:23:26 +00:00
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
2025-04-04 17:10:15 +00:00
sleep 2
2025-04-04 17:23:26 +00:00
info "Attempting broader pkill for lingering database daemons..."
sudo pkill -f mariadbd || sudo pkill -f mysqld || true # Ignore errors if not found
2025-04-04 17:10:15 +00:00
sleep 3
2025-04-04 17:23:26 +00:00
success "Safe mode processes likely stopped."
2025-04-04 17:10:15 +00:00
2025-04-04 17:23:26 +00:00
info " Starting $DB_SERVICE_NAME service normally... "
if ! sudo systemctl start " $DB_SERVICE_NAME " ; then error_exit " Failed to start $DB_SERVICE_NAME service after password reset. Check status/logs. " ; fi
2025-04-04 17:10:15 +00:00
sleep 5 # Allow time for service to initialize
2025-04-04 17:23:26 +00:00
info " Verifying $DB_SERVICE_NAME service status... "
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
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
2025-03-31 17:37:54 +00:00
else
2025-04-04 17:10:15 +00:00
info "Using provided root password to create database and user."
# 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
2025-04-04 17:23:26 +00:00
error_exit " Failed to connect to database using provided root credentials. Check user ' $DB_ROOT_USER ', password, and host ' $DB_HOST '. "
2025-04-04 17:10:15 +00:00
fi
success "Database root connection successful."
2025-03-31 17:37:54 +00:00
fi
2025-04-04 17:10:15 +00:00
# --- Create WordPress Database and User ---
info " Creating WordPress database ' $DB_NAME ' and user ' $DB_USER '... "
# Use printf for safer password injection into the command
2025-04-04 17:23:26 +00:00
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;" \
2025-04-04 17:10:15 +00:00
" $DB_NAME " " $DB_USER " " $DB_HOST " " $DB_PASSWORD " " $DB_NAME " " $DB_USER " " $DB_HOST " )
2025-03-31 17:37:54 +00:00
2025-04-04 17:27:20 +00:00
# Try connecting to localhost via socket first if TCP connection fails
2025-04-04 17:10:15 +00:00
if ! mysql -u " $DB_ROOT_USER " -p" $DB_ROOT_PASS " -h " $DB_HOST " -e " $SQL_COMMAND " ; then
2025-04-04 17:27:20 +00:00
warning " Failed to connect via TCP ( $DB_HOST ). Trying socket connection to localhost... "
if ! mysql -u " $DB_ROOT_USER " -p" $DB_ROOT_PASS " --protocol= socket -e " $SQL_COMMAND " ; then
# If socket fails too, try with no password (in case reset made blank password)
warning "Socket connection failed too. Trying without password..."
if ! mysql -u " $DB_ROOT_USER " --protocol= socket -e " $SQL_COMMAND " ; then
error_exit " Failed to execute SQL command to create WordPress database/user. Check MySQL/MariaDB logs and permissions for ' $DB_ROOT_USER '. "
else
warning "Connected without password! MySQL root has no password now."
# Update the root password again to be sure
SECURE_ROOT_SQL = " ALTER USER 'root'@'localhost' IDENTIFIED BY ' $DB_ROOT_PASS '; FLUSH PRIVILEGES; "
mysql -u " $DB_ROOT_USER " --protocol= socket -e " $SECURE_ROOT_SQL " || warning "Could not secure root user with password!"
fi
fi
success " Database ' $DB_NAME ' and user ' $DB_USER ' created successfully via socket connection. "
else
success " Database ' $DB_NAME ' and user ' $DB_USER ' created successfully. "
2025-03-31 17:37:54 +00:00
fi
2025-04-04 17:23:26 +00:00
# --- WordPress Core File Setup ---
info " Ensuring WordPress files are present in: $WP_ROOT "
2025-04-04 17:10:15 +00:00
cd " $WP_ROOT " || error_exit " Failed to change directory to $WP_ROOT "
2025-03-31 17:37:54 +00:00
2025-04-04 17:10:15 +00:00
# Backup existing wp-config.php if it exists
if [ [ -f "wp-config.php" ] ] ; then
2025-04-04 17:23:26 +00:00
BACKUP_NAME = " wp-config.php.bak. $( date +%Y%m%d_%H%M%S) " # Use underscore in timestamp
2025-04-04 17:10:15 +00:00
info " Backing up existing wp-config.php to $BACKUP_NAME "
2025-04-04 17:23:26 +00:00
cp wp-config.php " $BACKUP_NAME " || warning "Failed to backup wp-config.php"
2025-03-31 17:37:54 +00:00
fi
2025-04-04 17:23:26 +00:00
# Download WordPress core files ONLY if key files/dirs are missing
if [ [ ! -f "index.php" || ! -f "wp-includes/version.php" || ! -d "wp-admin" ] ] ; then
info "WordPress core files seem missing or incomplete. Downloading..."
# Use determined $SUDO_CMD, $WP_EXECUTABLE, and $WP_RUN_ARGS
if ! $SUDO_CMD $WP_EXECUTABLE core download " ${ WP_RUN_ARGS [@] } " --skip-content --version= latest; then
error_exit "Failed to download WordPress core files using WP-CLI."
2025-04-04 17:10:15 +00:00
fi
success "WordPress core downloaded."
2025-04-04 15:55:18 +00:00
else
2025-04-04 17:23:26 +00:00
info "WordPress core files seem to exist. Skipping download."
2025-03-31 17:37:54 +00:00
fi
2025-04-04 17:10:15 +00:00
# --- Create wp-config.php ---
info "Creating wp-config.php..."
2025-04-04 17:23:26 +00:00
# Generate Salts using WP-CLI
info "Generating WordPress salts using WP-CLI..."
# Use determined $SUDO_CMD, $WP_EXECUTABLE, and $WP_RUN_ARGS
SALTS = $( $SUDO_CMD $WP_EXECUTABLE config salt generate --raw " ${ WP_RUN_ARGS [@] } " 2>/dev/null) || {
warning "Could not generate salts using WP-CLI. Falling back to openssl (less standard format)."
SALTS = $( cat <<EOF
define( 'AUTH_KEY' , '$(generate_password)' ) ;
define( 'SECURE_AUTH_KEY' , '$(generate_password)' ) ;
define( 'LOGGED_IN_KEY' , '$(generate_password)' ) ;
define( 'NONCE_KEY' , '$(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."
2025-04-04 17:10:15 +00:00
# Use cat with heredoc for wp-config.php creation
2025-04-04 17:23:26 +00:00
cat > wp-config.php <<EOF || error_exit "Failed to write wp-config.php file."
2025-04-04 14:55:12 +00:00
<?php
2025-04-04 15:55:18 +00:00
/**
* The base configuration for WordPress
2025-04-04 17:10:15 +00:00
* @link https://wordpress.org/support/article/editing-wp-config-php/
2025-04-04 15:55:18 +00:00
* @package WordPress
*/
// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
2025-04-04 14:55:12 +00:00
define( 'DB_NAME' , '${DB_NAME}' ) ;
2025-04-04 15:55:18 +00:00
/** Database username */
2025-04-04 14:55:12 +00:00
define( 'DB_USER' , '${DB_USER}' ) ;
2025-04-04 15:55:18 +00:00
/** Database password */
2025-04-04 14:55:12 +00:00
define( 'DB_PASSWORD' , '${DB_PASSWORD}' ) ;
2025-04-04 15:55:18 +00:00
/** Database hostname */
2025-04-04 14:55:12 +00:00
define( 'DB_HOST' , '${DB_HOST}' ) ;
2025-04-04 15:55:18 +00:00
/** Database charset to use in creating database tables. */
2025-04-04 17:23:26 +00:00
define( 'DB_CHARSET' , 'utf8mb4' ) ;
2025-04-04 15:55:18 +00:00
/** The database collate type. Don' t change this if in doubt. */
2025-04-04 14:55:12 +00:00
define( 'DB_COLLATE' , '' ) ;
2025-04-04 17:10:15 +00:00
/**#@+
2025-04-04 15:55:18 +00:00
* Authentication unique keys and salts.
2025-04-04 17:23:26 +00:00
* @since 2.6.0
2025-04-04 15:55:18 +00:00
*/
2025-04-04 17:10:15 +00:00
${ SALTS }
/**#@-*/
2025-04-04 14:55:12 +00:00
2025-04-04 15:55:18 +00:00
/**
* WordPress database table prefix.
*/
2025-04-04 17:23:26 +00:00
\$ table_prefix = 'wp_' ; // Default prefix
2025-04-04 14:55:12 +00:00
2025-04-04 15:55:18 +00:00
/**
* For developers: WordPress debugging mode.
*/
2025-04-04 17:23:26 +00:00
define( 'WP_DEBUG' , false ) ; // Set to true for development
2025-04-04 14:55:12 +00:00
2025-04-04 17:10:15 +00:00
/* Add any custom values between this line and the "stop editing" line. */
2025-04-04 17:23:26 +00:00
// Memory Limits
2025-04-04 17:10:15 +00:00
define( 'WP_MEMORY_LIMIT' , '256M' ) ;
2025-04-04 17:23:26 +00:00
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
2025-04-04 17:10:15 +00:00
// Define site URL and home URL ( ensure protocol matches server setup)
define( 'WP_HOME' , 'https://${DOMAIN}' ) ;
define( 'WP_SITEURL' , 'https://${DOMAIN}' ) ;
2025-04-04 17:23:26 +00:00
// If using a reverse proxy handling SSL, uncomment the following:
2025-04-04 17:10:15 +00:00
// if ( isset( \$ _SERVER[ 'HTTP_X_FORWARDED_PROTO' ] ) && \$ _SERVER[ 'HTTP_X_FORWARDED_PROTO' ] = = = 'https' ) {
// \$ _SERVER[ 'HTTPS' ] = 'on' ;
// }
2025-04-04 17:23:26 +00:00
// Fix missing HTTP_HOST for CLI operations ( redundant if using WP_CLI_CONFIG_PATH, but safe)
2025-04-04 17:10:15 +00:00
if ( defined( 'WP_CLI' ) && WP_CLI && ! isset( \$ _SERVER[ 'HTTP_HOST' ] ) ) {
2025-04-04 15:55:18 +00:00
\$ _SERVER[ 'HTTP_HOST' ] = '${DOMAIN}' ;
}
2025-04-04 17:10:15 +00:00
/* That' s all, stop editing! Happy publishing. */
2025-04-04 15:55:18 +00:00
/** Absolute path to the WordPress directory. */
2025-04-04 14:55:12 +00:00
if ( ! defined( 'ABSPATH' ) ) {
2025-04-04 17:10:15 +00:00
define( 'ABSPATH' , __DIR__ . '/' ) ;
2025-04-04 14:55:12 +00:00
}
2025-04-04 15:55:18 +00:00
/** Sets up WordPress vars and included files. */
2025-04-04 14:55:12 +00:00
require_once ABSPATH . 'wp-settings.php' ;
EOF
2025-04-04 17:10:15 +00:00
success "wp-config.php created successfully."
2025-04-04 15:55:18 +00:00
2025-04-04 17:10:15 +00:00
# --- Set Permissions ---
2025-04-04 17:23:26 +00:00
info " Setting file permissions for $WP_ROOT ... "
# Use sudo explicitly as permissions/ownership changes usually require root
# Set ownership first - recursively
info " Setting ownership to ${ WEB_USER } : ${ WEB_GROUP } ... "
2025-04-04 17:10:15 +00:00
if ! sudo chown -R " ${ WEB_USER } : ${ WEB_GROUP } " " $WP_ROOT " ; then
2025-04-04 17:23:26 +00:00
# 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. "
2025-04-04 17:10:15 +00:00
fi
# Set directory and file permissions
2025-04-04 17:23:26 +00:00
info "Setting directory permissions to 755 and file permissions to 644..."
# Use sudo find. Requires runner (root) to have read access everywhere.
if ! sudo find " $WP_ROOT " -type d -exec chmod 755 { } \; ; then
warning "Could not set directory permissions using find. Check permissions."
2025-04-04 17:10:15 +00:00
fi
2025-04-04 17:23:26 +00:00
if ! sudo find " $WP_ROOT " -type f -exec chmod 644 { } \; ; then
warning "Could not set file permissions using find. Check permissions."
2025-04-04 17:10:15 +00:00
fi
2025-04-04 17:23:26 +00:00
# Ensure wp-config.php is more secure (readable only by owner and group)
info "Setting specific permissions for wp-config.php to 640..."
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."
2025-04-04 17:10:15 +00:00
fi
2025-04-04 17:23:26 +00:00
success "File permissions set (potential warnings noted)."
2025-03-31 17:37:54 +00:00
2025-04-04 17:10:15 +00:00
# --- WordPress Core Installation ---
2025-04-04 17:23:26 +00:00
info "Checking if WordPress is already installed..."
# Use determined $SUDO_CMD, $WP_EXECUTABLE, and $WP_RUN_ARGS
if ! $SUDO_CMD $WP_EXECUTABLE core is-installed " ${ WP_RUN_ARGS [@] } " ; then
2025-04-04 17:10:15 +00:00
info "WordPress is not installed. Proceeding with installation..."
2025-04-04 17:23:26 +00:00
# Use determined $SUDO_CMD, $WP_EXECUTABLE, and $WP_RUN_ARGS
if ! $SUDO_CMD $WP_EXECUTABLE core install \
--url= " https:// ${ DOMAIN } " \
2025-04-04 17:10:15 +00:00
--title= " My WordPress Site on $DOMAIN " \
2025-03-31 17:37:54 +00:00
--admin_user= " $WP_ADMIN_USER " \
--admin_password= " $WP_ADMIN_PASS " \
--admin_email= " $WP_ADMIN_EMAIL " \
2025-04-04 17:10:15 +00:00
--skip-email \
2025-04-04 17:23:26 +00:00
" ${ WP_RUN_ARGS [@] } " ; then
error_exit "WordPress core installation failed using WP-CLI."
2025-04-04 17:10:15 +00:00
fi
2025-03-31 17:37:54 +00:00
2025-04-04 17:10:15 +00:00
info "Removing default plugins (Akismet, Hello Dolly)..."
2025-04-04 17:23:26 +00:00
# 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)."
2025-04-04 17:10:15 +00:00
2025-04-04 17:23:26 +00:00
success "WordPress installed successfully via WP-CLI."
2025-03-31 17:37:54 +00:00
else
2025-04-04 17:23:26 +00:00
info "WordPress is already installed according to WP-CLI."
# Optionally update URL if needed (be careful with this, can break site if proxy/etc involved)
# info "Verifying site URL options..."
# $SUDO_CMD $WP_EXECUTABLE option update siteurl "https://$DOMAIN" "${WP_RUN_ARGS[@]}" || warning "Failed to update siteurl option"
# $SUDO_CMD $WP_EXECUTABLE option update home "https://$DOMAIN" "${WP_RUN_ARGS[@]}" || warning "Failed to update home option"
2025-04-04 17:10:15 +00:00
fi
# --- Final Summary ---
2025-04-04 17:23:26 +00:00
success "WordPress setup process completed!"
2025-04-04 17:10:15 +00:00
printf " \n--- ${ YELLOW } Installation Summary ${ NC } ---\n "
printf " Site URL: ${ GREEN } https://%s ${ NC } \n " " $DOMAIN "
printf " WP Root: ${ GREEN } %s ${ NC } \n " " $WP_ROOT "
printf " Web User: ${ GREEN } %s ${ NC } \n " " $WEB_USER "
printf " Web Group: ${ GREEN } %s ${ NC } \n " " $WEB_GROUP "
printf "\n"
printf " ${ YELLOW } Admin Credentials: ${ NC } \n "
printf " Username: ${ GREEN } %s ${ NC } \n " " $WP_ADMIN_USER "
printf " Password: ${ YELLOW } %s ${ NC } (Keep this safe!)\n " " $WP_ADMIN_PASS "
printf " Email: ${ GREEN } %s ${ NC } \n " " $WP_ADMIN_EMAIL "
printf "\n"
printf " ${ YELLOW } Database Credentials: ${ NC } \n "
printf " Database Name: ${ GREEN } %s ${ NC } \n " " $DB_NAME "
printf " Username: ${ GREEN } %s ${ NC } \n " " $DB_USER "
printf " Password: ${ YELLOW } %s ${ NC } (Keep this safe!)\n " " $DB_PASSWORD "
printf " Host: ${ GREEN } %s ${ NC } \n " " $DB_HOST "
if [ [ " $PERFORM_DB_ROOT_RESET " = = "true" ] ] ; then
2025-04-04 17:23:26 +00:00
printf " \n ${ RED } IMPORTANT: The MySQL/MariaDB root password was reset during this process. ${ NC } \n "
printf " New Root Pass: ${ YELLOW } %s ${ NC } (Keep this safe!)\n " " $DB_ROOT_PASS "
2025-03-31 17:37:54 +00:00
fi
2025-04-04 17:31:11 +00:00
printf "%s\n" "---------------------------"
2025-03-31 17:37:54 +00:00
2025-04-04 17:23:26 +00:00
# Explicitly call cleanup before final exit (trap should also handle it)
cleanup
2025-04-04 17:10:15 +00:00
exit 0