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.
# Includes dependency checks, WP-CLI installation, database setup,
# WordPress core installation, and configuration.
# v2.1 - Fixes WP-CLI execution path issues when run as root.
#
# --- Configuration ---
@ -22,8 +23,6 @@ BLUE='\033[0;34m'
NC='\033[0m' # No Color
# --- 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_PASS=""
WP_ADMIN_EMAIL=""
@ -32,9 +31,10 @@ 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
SKIP_DB_ROOT_RESET="false" # By default, perform the root password reset (use --skip-db-root-reset to disable)
WEB_USER="litespeed" # Web server user (e.g., www-data, apache, nginx)
# PERFORM_DB_ROOT_RESET is set during arg parsing
WEB_USER="litespeed" # Web server user
WEB_GROUP="litespeed" # Web server group
WP_CLI_PATH="/usr/local/bin/wp" # Path to WP-CLI executable
# --- Helper Functions ---
@ -56,6 +56,8 @@ warning() {
# Print error messages and exit
error_exit() {
printf "${RED}[ERROR] %s${NC}\n" "$@" >&2
# Attempt cleanup before exiting
cleanup &> /dev/null || true
exit 1
}
@ -69,7 +71,8 @@ usage() {
printf " --wpusername=USERNAME WordPress admin username (mandatory)\n"
printf " --wppassword=PASSWORD WordPress admin password (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 "Optional Options:\n"
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 " --webgroup=GROUP Web server group (default: %s)\n" "$WEB_GROUP"
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 "\n"
printf "Example:\n"
@ -93,28 +96,30 @@ command_exists() {
# Function to generate a random secure password
generate_password() {
openssl rand -base64 16 # Increased length slightly
openssl rand -base64 16
}
# 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() {
info "Cleaning up temporary files..."
rm -f "$WP_CLI_CONFIG_PATH"
# Add any other cleanup tasks here
# 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
}
# --- 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" -- "$@")
if [ $? != 0 ]; then
error_exit "Terminating... Invalid arguments."
error_exit "Terminating... Invalid arguments provided."
fi
# Note the quotes around "$TEMP": they are essential!
eval set -- "$TEMP"
unset TEMP
PERFORM_DB_ROOT_RESET="false"
PERFORM_DB_ROOT_RESET="false" # Default value
while true; do
case "$1" in
@ -129,90 +134,161 @@ while true; do
--webuser) WEB_USER="$2"; shift 2 ;;
--webgroup) WEB_GROUP="$2"; shift 2 ;;
-h|--help) usage ;;
--) shift ; break ;; # End of options
*) error_exit "Internal error! Unexpected option: $1";;
--) shift ; break ;;
*) error_exit "Internal error parsing options! Unexpected option: $1";;
esac
done
# Set trap *after* WP_CLI_CONFIG_PATH is defined and potentially used
trap cleanup EXIT SIGINT SIGTERM
# --- 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
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" == "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 ! 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
success "Parameters validated."
# --- Determine Domain ---
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)
# 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-)//')
if [[ -z "$DOMAIN" || "$DOMAIN" == "$FULL_HOSTNAME" ]]; then
warning "Could not reliably determine domain from hostname '$FULL_HOSTNAME'. Using it as is."
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."
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
info "Auto-detected domain: $DOMAIN (Use --domain to override)"
info "Auto-detected domain: $DOMAIN (Use --domain to override if incorrect)"
else
info "Using specified domain: $DOMAIN"
fi
# --- Dependency Checks ---
info "Checking dependencies..."
declare -a dependencies=("php" "mysql" "curl" "openssl" "sudo" "hostname" "sed" "systemctl" "getopt")
for cmd in "${dependencies[@]}"; do
if ! command_exists "$cmd"; then
error_exit "Required command '$cmd' is not installed. Please install it first."
fi
done
# Specific check for pkill if reset is chosen
# 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
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 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
success "All dependencies found."
success "All essential dependencies found."
# --- WP-CLI Setup ---
WP_CLI_PATH="/usr/local/bin/wp"
# Check if WP-CLI is installed and executable
if ! command_exists wp; then
info "WP-CLI not found. Installing WP-CLI..."
if ! curl -o wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar; then
# 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
error_exit "Failed to download WP-CLI."
fi
chmod +x wp-cli.phar
if ! sudo mv wp-cli.phar "$WP_CLI_PATH"; then
error_exit "Failed to move WP-CLI to $WP_CLI_PATH. Check sudo permissions."
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."
fi
# Verify installation
if ! command_exists wp; then
error_exit "WP-CLI installation failed unexpectedly."
# 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."
fi
success "WP-CLI installed successfully to $WP_CLI_PATH"
else
success "WP-CLI is already installed."
success "WP-CLI is already installed at $WP_CLI_PATH."
fi
# Set up temporary WP-CLI config for HTTP_HOST if needed
export WP_CLI_CONFIG_PATH="/tmp/wp-cli-config-$RANDOM.yml"
cat > "$WP_CLI_CONFIG_PATH" <<EOF
# Temporary config to help WP-CLI resolve the site URL
# This might not always be necessary depending on server config
# --- 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.
apache_modules:
- mod_rewrite
_:
server:
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
# Set trap to clean up the temp file on exit
trap cleanup EXIT
# 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
# --- Database Setup ---
info "Setting up database..."
@ -222,77 +298,100 @@ DB_PASSWORD=$(generate_password)
if [[ "$PERFORM_DB_ROOT_RESET" == "true" ]]; then
# --- 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!"
new_root_password=$(generate_password)
info "New root password will be: $new_root_password"
info "Stopping MariaDB service..."
if ! sudo systemctl stop mariadb; then error_exit "Failed to stop MariaDB service."; fi
# Wait a moment to ensure it stopped
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."
sleep 3
info "Starting MariaDB in safe mode (skip-grant-tables)..."
sudo mysqld_safe --skip-grant-tables --skip-networking &
MYSQLD_SAFE_PID=$!
# Wait for MariaDB to likely start in safe mode
sleep 10 # Increased wait time for reliability
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
info "Attempting to reset root password..."
# Try multiple common root user variations
if ! sudo mysql --protocol=socket -u root <<-EOF
# Use sudo explicitly for safe mode start
sudo "$MYSQLD_SAFE_CMD" --skip-grant-tables --skip-networking &
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;
ALTER USER 'root'@'localhost' IDENTIFIED BY '$new_root_password';
ALTER USER 'root'@'127.0.0.1' IDENTIFIED BY '$new_root_password';
FLUSH PRIVILEGES;
EXIT
EOF
then
warning "Failed initial root password reset attempt. Trying alternative command..."
# Fallback for older MySQL/MariaDB versions
if ! sudo mysql --protocol=socket -u root <<-EOF
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';
FLUSH PRIVILEGES;
EXIT
EOF
then
warning "Failed 'ALTER USER root@127.0.0.1' reset attempt. Trying legacy 'UPDATE' method..."
# Fallback for older MySQL/MariaDB versions
if ! sudo mysql --protocol=socket -u root <<-EOF &> /dev/null
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
then
warning "Both root password reset methods failed. Trying grant table directly (less reliable)."
# This might be needed on some very old or strangely configured systems
if ! sudo mysql --protocol=socket -u mysql <<-EOF
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
FLUSH PRIVILEGES;
UPDATE mysql.user SET authentication_string=PASSWORD('$new_root_password') WHERE User='root';
FLUSH PRIVILEGES;
EXIT
EOF
then
error_exit "All attempts to reset the root password failed. Check MySQL/MariaDB logs. Manual intervention required."
fi
then
error_exit "All attempts to reset the root password in safe mode failed. Check MySQL/MariaDB logs. Manual intervention required."
fi
fi
fi
fi
success "Root password likely reset in safe mode."
info "Stopping MariaDB safe mode process..."
# Attempt to kill the background mysqld_safe process gracefully first, then force if needed
sudo kill $MYSQLD_SAFE_PID || true # Try killing the specific PID
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?)."
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
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
success "Safe mode processes likely stopped."
info "Starting MariaDB service normally..."
if ! sudo systemctl start mariadb; then error_exit "Failed to start MariaDB service after password reset."; fi
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
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
success "MariaDB service started successfully with new root password."
DB_ROOT_PASS="$new_root_password" # Use the newly set password
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
else
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
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
success "Database root connection successful."
fi
@ -300,141 +399,125 @@ fi
# --- Create WordPress Database and User ---
info "Creating WordPress database '$DB_NAME' and user '$DB_USER'..."
# 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")
# 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
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
success "Database and user created successfully."
success "Database '$DB_NAME' and user '$DB_USER' created successfully."
# --- WordPress Installation ---
info "Navigating to WordPress root: $WP_ROOT"
# --- WordPress Core File Setup ---
info "Ensuring WordPress files are present in: $WP_ROOT"
cd "$WP_ROOT" || error_exit "Failed to change directory to $WP_ROOT"
# Backup existing wp-config.php if it exists
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"
cp wp-config.php "$BACKUP_NAME"
cp wp-config.php "$BACKUP_NAME" || warning "Failed to backup wp-config.php"
fi
# Download WordPress core files if necessary (index.php is a good indicator)
# Run wp commands as the web user if possible, falling back to --allow-root if needed
WP_RUN_ARGS=("--path=$WP_ROOT")
# Determine if sudo is needed to run as web user
SUDO_CMD=""
if [[ "$(id -u)" -ne "$(id -u "$WEB_USER")" ]]; then
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."
# 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."
fi
success "WordPress core downloaded."
else
info "WordPress files already exist. Skipping download."
info "WordPress core files seem to exist. Skipping download."
fi
# --- 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..."
# Generate Salts using WP-CLI if possible, otherwise fallback to openssl
SALTS=$($SUDO_CMD wp 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)."
echo "define( 'AUTH_KEY', '$(generate_password)' );"
echo "define( 'SECURE_AUTH_KEY', '$(generate_password)' );"
echo "define( 'LOGGED_IN_KEY', '$(generate_password)' );"
echo "define( 'NONCE_KEY', '$(generate_password)' );"
echo "define( 'AUTH_SALT', '$(generate_password)' );"
echo "define( 'SECURE_AUTH_SALT', '$(generate_password)' );"
echo "define( 'LOGGED_IN_SALT', '$(generate_password)' );"
echo "define( 'NONCE_SALT', '$(generate_password)' );"
})
# 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."
# 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
/**
* The base configuration for WordPress
*
* @link https://wordpress.org/support/article/editing-wp-config-php/
*
* @package WordPress
*/
// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', '${DB_NAME}' );
/** Database username */
define( 'DB_USER', '${DB_USER}' );
/** Database password */
define( 'DB_PASSWORD', '${DB_PASSWORD}' );
/** Database hostname */
define( 'DB_HOST', '${DB_HOST}' );
/** 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. */
define( 'DB_COLLATE', '' );
/**#@+
* Authentication unique keys and salts.
* Generate these at: https://api.wordpress.org/secret-key/1.1/salt/
* You can change these at any point to invalidate all existing cookies. This will force all users to have to log in again.
* @since 2.6.0
*/
${SALTS}
/**#@-*/
/**
* 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.
* 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. */
// Memory Limits
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( 'WP_HOME', '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') {
// \$_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'] ) ) {
\$_SERVER['HTTP_HOST'] = '${DOMAIN}';
}
@ -451,61 +534,69 @@ if ( ! defined( 'ABSPATH' ) ) {
require_once ABSPATH . 'wp-settings.php';
EOF
if [[ ! -f "wp-config.php" ]]; then
error_exit "Failed to create wp-config.php file."
fi
success "wp-config.php created successfully."
# --- Set Permissions ---
info "Setting file permissions..."
# Set ownership first
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}..."
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
# Set directory and file permissions
# Ensure the script runner has write access to the directory to run find
cd "$WP_ROOT" || error_exit "Failed to cd into $WP_ROOT before setting permissions"
if ! sudo find . -type d -exec chmod 755 {} \; ; then
warning "Could not set directory permissions using find. Check sudo permissions."
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."
fi
if ! sudo find . -type f -exec chmod 644 {} \; ; then
warning "Could not set file permissions using find. Check sudo permissions."
if ! sudo find "$WP_ROOT" -type f -exec chmod 644 {} \; ; then
warning "Could not set file permissions using find. Check permissions."
fi
# Ensure wp-config.php is readable by the web server, but not world-writable
if ! sudo chmod 640 wp-config.php; then # More secure permission
warning "Could not set specific permissions on wp-config.php."
# 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."
fi
success "File permissions set."
success "File permissions set (potential warnings noted)."
# --- WordPress Core Installation ---
# Check if WordPress is already installed before attempting installation
if ! $SUDO_CMD wp core is-installed "${WP_RUN_ARGS[@]}"; then
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
info "WordPress is not installed. Proceeding with installation..."
if ! $SUDO_CMD wp core install \
--url="https://$DOMAIN" \
# Use determined $SUDO_CMD, $WP_EXECUTABLE, and $WP_RUN_ARGS
if ! $SUDO_CMD $WP_EXECUTABLE core install \
--url="https://${DOMAIN}" \
--title="My WordPress Site on $DOMAIN" \
--admin_user="$WP_ADMIN_USER" \
--admin_password="$WP_ADMIN_PASS" \
--admin_email="$WP_ADMIN_EMAIL" \
--skip-email \
"${WP_RUN_ARGS[@]}"; then # Added skip-email
error_exit "WordPress core installation failed."
"${WP_RUN_ARGS[@]}"; then
error_exit "WordPress core installation failed using WP-CLI."
fi
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
info "WordPress is already installed."
# Optionally update URL if needed (be careful with this)
# info "Verifying site URL..."
# $SUDO_CMD wp option update siteurl "https://$DOMAIN" "${WP_RUN_ARGS[@]}"
# $SUDO_CMD wp option update home "https://$DOMAIN" "${WP_RUN_ARGS[@]}"
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"
fi
# --- Final Summary ---
success "WordPress setup completed!"
success "WordPress setup process completed!"
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"
@ -523,9 +614,12 @@ 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
printf "\n${RED}IMPORTANT: The MySQL/MariaDB root password was reset to:${NC}\n"
printf " Root Password: ${YELLOW}%s${NC}\n" "$DB_ROOT_PASS"
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"
fi
printf "---------------------------\n"
# Explicitly call cleanup before final exit (trap should also handle it)
cleanup
exit 0