1072 lines
49 KiB
Bash
1072 lines
49 KiB
Bash
#!/bin/bash
|
||
#
|
||
# 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 ---
|
||
|
||
# Exit immediately if a command exits with a non-zero status.
|
||
set -e
|
||
# 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
|
||
|
||
# Colors for output messages
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# --- Default Values ---
|
||
WP_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
|
||
# 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
|
||
NEEDS_OWNERSHIP_CORRECTION="false" # Flag to track if ownership correction is needed
|
||
|
||
# --- Helper Functions ---
|
||
|
||
# Print informational messages
|
||
info() {
|
||
printf "${BLUE}[INFO] %s${NC}\n" "$@"
|
||
}
|
||
|
||
# Print success messages
|
||
success() {
|
||
printf "${GREEN}[SUCCESS] %s${NC}\n" "$@"
|
||
}
|
||
|
||
# Print warning messages
|
||
warning() {
|
||
printf "${YELLOW}[WARNING] %s${NC}\n" "$@"
|
||
}
|
||
|
||
# Print error messages and exit
|
||
error_exit() {
|
||
printf "${RED}[ERROR] %s${NC}\n" "$@" >&2
|
||
# Attempt cleanup before exiting
|
||
cleanup &> /dev/null || true
|
||
exit 1
|
||
}
|
||
|
||
# Function to display usage information
|
||
usage() {
|
||
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"
|
||
# 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"
|
||
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"
|
||
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"
|
||
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"
|
||
exit 1
|
||
}
|
||
|
||
# Function to check if a command exists
|
||
command_exists() {
|
||
command -v "$1" &> /dev/null
|
||
}
|
||
|
||
# Function to generate a random secure password
|
||
generate_password() {
|
||
openssl rand -base64 16
|
||
}
|
||
|
||
# Function to correct ownership after WP-CLI operations if needed
|
||
correct_ownership_if_needed() {
|
||
if [[ "$NEEDS_OWNERSHIP_CORRECTION" == "true" ]]; then
|
||
info "Correcting file ownership after WP-CLI operation..."
|
||
if ! sudo chown -R "${WEB_USER}:${WEB_GROUP}" "$WP_ROOT" 2>/dev/null; then
|
||
warning "Failed to correct some file ownership. Some files may still be owned by root."
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# 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() {
|
||
# 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 ---
|
||
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 provided."
|
||
fi
|
||
|
||
eval set -- "$TEMP"
|
||
unset TEMP
|
||
|
||
PERFORM_DB_ROOT_RESET="false" # Default value
|
||
|
||
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 ;;
|
||
--) 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: '$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. 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 install it or specify --domain."; fi
|
||
FULL_HOSTNAME=$(hostname -f)
|
||
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 if incorrect)"
|
||
else
|
||
info "Using specified domain: $DOMAIN"
|
||
fi
|
||
|
||
# --- Dependency Checks ---
|
||
info "Checking dependencies..."
|
||
# 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
|
||
# 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 essential dependencies found."
|
||
|
||
# --- WP-CLI Setup ---
|
||
# 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 "$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 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 at $WP_CLI_PATH."
|
||
fi
|
||
|
||
# --- WP-CLI Execution Context Setup ---
|
||
# Determine how WP-CLI commands should be run (user, flags)
|
||
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
|
||
|
||
# Prefer running WP-CLI as the designated web user so that any files it
|
||
# creates/updates (e.g. .htaccess) are owned by that user instead of root.
|
||
if [[ "$(id -u)" -eq 0 ]]; then
|
||
info "Script is running as root. Attempting to run WP-CLI as '$WEB_USER' for correct file ownership."
|
||
if sudo -n -u "$WEB_USER" "$WP_EXECUTABLE" --info --skip-update --quiet "${WP_RUN_ARGS[@]}" &>/dev/null; then
|
||
SUDO_CMD="sudo -u $WEB_USER"
|
||
info "WP-CLI will be executed via sudo as '$WEB_USER'."
|
||
else
|
||
warning "Failed to execute WP-CLI as '$WEB_USER' without password. Falling back to running as root with --allow-root. Files will be corrected to proper ownership after creation."
|
||
WP_RUN_ARGS+=("--allow-root")
|
||
# Set flag to indicate we need ownership correction after WP-CLI operations
|
||
NEEDS_OWNERSHIP_CORRECTION="true"
|
||
fi
|
||
else
|
||
# Script is NOT running as root.
|
||
if [[ "$(id -u)" -eq "$(id -u "$WEB_USER")" ]]; then
|
||
info "Script is already running as the web user ('$WEB_USER'). No sudo or --allow-root needed."
|
||
else
|
||
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
|
||
SUDO_CMD="sudo -u $WEB_USER"
|
||
info "Successfully configured sudo execution for WP-CLI as '$WEB_USER'."
|
||
else
|
||
error_exit "Unable to execute WP-CLI as '$WEB_USER'. Ensure the current user has passwordless sudo access, or run this script as root."
|
||
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, 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
|
||
# 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..."
|
||
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 ---
|
||
# 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"
|
||
|
||
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 $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 &
|
||
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
|
||
|
||
# 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"
|
||
|
||
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 'ALTER USER' reset attempt (may be normal). Trying alternative syntax..."
|
||
# Try the mysql_native_password plugin explicitly
|
||
if ! sudo mysql --protocol=socket -u root <<-EOF &> /dev/null
|
||
FLUSH PRIVILEGES;
|
||
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';
|
||
FLUSH PRIVILEGES;
|
||
EXIT
|
||
EOF
|
||
then
|
||
warning "Failed with native password plugin. 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 "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 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 (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
|
||
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
|
||
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 $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
|
||
|
||
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 '$DB_ROOT_USER', password, and host '$DB_HOST'."
|
||
fi
|
||
success "Database root connection successful."
|
||
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;" \
|
||
"$DB_NAME" "$DB_USER" "$DB_HOST" "$DB_PASSWORD" "$DB_NAME" "$DB_USER" "$DB_HOST")
|
||
|
||
# Try connecting to localhost via socket first if TCP connection fails
|
||
if ! mysql -u "$DB_ROOT_USER" -p"$DB_ROOT_PASS" -h "$DB_HOST" -e "$SQL_COMMAND"; then
|
||
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."
|
||
fi
|
||
|
||
# --- 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)" # Use underscore in timestamp
|
||
info "Backing up existing wp-config.php to $BACKUP_NAME"
|
||
cp wp-config.php "$BACKUP_NAME" || warning "Failed to backup wp-config.php"
|
||
fi
|
||
|
||
# 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[@]}" --version=latest; then
|
||
error_exit "Failed to download WordPress core files using WP-CLI."
|
||
fi
|
||
success "WordPress core downloaded."
|
||
correct_ownership_if_needed
|
||
else
|
||
info "WordPress core files seem to exist. Skipping download."
|
||
fi
|
||
|
||
# --- Create wp-config.php ---
|
||
info "Creating wp-config.php..."
|
||
# 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 || 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' );
|
||
/** The database collate type. Don't change this if in doubt. */
|
||
define( 'DB_COLLATE', '' );
|
||
|
||
/**#@+
|
||
* Authentication unique keys and salts.
|
||
* @since 2.6.0
|
||
*/
|
||
${SALTS}
|
||
/**#@-*/
|
||
|
||
/**
|
||
* WordPress database table prefix.
|
||
*/
|
||
\$table_prefix = 'wp_'; // Default prefix
|
||
|
||
/**
|
||
* For developers: WordPress debugging mode.
|
||
*/
|
||
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_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 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 (redundant if using WP_CLI_CONFIG_PATH, but safe)
|
||
if ( defined( 'WP_CLI' ) && WP_CLI && ! isset( \$_SERVER['HTTP_HOST'] ) ) {
|
||
\$_SERVER['HTTP_HOST'] = '${DOMAIN}';
|
||
}
|
||
|
||
|
||
/* That's all, stop editing! Happy publishing. */
|
||
|
||
/** Absolute path to the WordPress directory. */
|
||
if ( ! defined( 'ABSPATH' ) ) {
|
||
define( 'ABSPATH', __DIR__ . '/' );
|
||
}
|
||
|
||
/** Sets up WordPress vars and included files. */
|
||
require_once ABSPATH . 'wp-settings.php';
|
||
EOF
|
||
|
||
success "wp-config.php created successfully."
|
||
|
||
# --- Set Permissions ---
|
||
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
|
||
# 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
|
||
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 "$WP_ROOT" -type f -exec chmod 644 {} \; ; then
|
||
warning "Could not set file permissions using find. Check permissions."
|
||
fi
|
||
# 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 (potential warnings noted)."
|
||
|
||
# --- WordPress Core Installation ---
|
||
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..."
|
||
# 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
|
||
error_exit "WordPress core installation failed using WP-CLI."
|
||
fi
|
||
correct_ownership_if_needed
|
||
|
||
info "Removing default plugins (Akismet, Hello Dolly)..."
|
||
# 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)."
|
||
correct_ownership_if_needed
|
||
|
||
# Install LiteSpeed Cache plugin (without activating yet)
|
||
info "Installing LiteSpeed Cache plugin..."
|
||
if $SUDO_CMD $WP_EXECUTABLE plugin install litespeed-cache "${WP_RUN_ARGS[@]}"; then
|
||
success "LiteSpeed Cache plugin installed successfully."
|
||
|
||
# Create necessary LiteSpeed Cache directories BEFORE activation
|
||
info "Creating LiteSpeed Cache directories before activation..."
|
||
LITESPEED_CACHE_DIR="$WP_ROOT/wp-content/litespeed"
|
||
if [[ ! -d "$LITESPEED_CACHE_DIR" ]]; then
|
||
sudo mkdir -p "$LITESPEED_CACHE_DIR"
|
||
sudo chown -R "${WEB_USER}:${WEB_GROUP}" "$LITESPEED_CACHE_DIR"
|
||
sudo chmod -R 755 "$LITESPEED_CACHE_DIR"
|
||
success "LiteSpeed Cache directory created: $LITESPEED_CACHE_DIR"
|
||
else
|
||
info "LiteSpeed Cache directory already exists: $LITESPEED_CACHE_DIR"
|
||
fi
|
||
|
||
# Create subdirectories that the plugin commonly uses
|
||
for subdir in css js img tmp; do
|
||
SUBDIR_PATH="$LITESPEED_CACHE_DIR/$subdir"
|
||
if [[ ! -d "$SUBDIR_PATH" ]]; then
|
||
sudo mkdir -p "$SUBDIR_PATH"
|
||
sudo chown -R "${WEB_USER}:${WEB_GROUP}" "$SUBDIR_PATH"
|
||
sudo chmod -R 755 "$SUBDIR_PATH"
|
||
fi
|
||
done
|
||
|
||
# Now activate the plugin after directories are ready
|
||
info "Activating LiteSpeed Cache plugin..."
|
||
if $SUDO_CMD $WP_EXECUTABLE plugin activate litespeed-cache "${WP_RUN_ARGS[@]}"; then
|
||
success "LiteSpeed Cache plugin activated successfully."
|
||
correct_ownership_if_needed
|
||
else
|
||
warning "Failed to activate LiteSpeed Cache plugin after installation."
|
||
fi
|
||
|
||
# Configure basic LiteSpeed Cache settings
|
||
info "Configuring basic LiteSpeed Cache settings..."
|
||
|
||
# Enable cache
|
||
$SUDO_CMD $WP_EXECUTABLE option update litespeed.conf.cache 1 "${WP_RUN_ARGS[@]}" || warning "Could not enable LiteSpeed cache"
|
||
|
||
# Set cache TTL to 1 week (604800 seconds)
|
||
$SUDO_CMD $WP_EXECUTABLE option update litespeed.conf.cache-ttl_pub 604800 "${WP_RUN_ARGS[@]}" || warning "Could not set public cache TTL"
|
||
|
||
# Enable CSS/JS optimization
|
||
$SUDO_CMD $WP_EXECUTABLE option update litespeed.conf.optm-css_min 1 "${WP_RUN_ARGS[@]}" || warning "Could not enable CSS minification"
|
||
$SUDO_CMD $WP_EXECUTABLE option update litespeed.conf.optm-js_min 1 "${WP_RUN_ARGS[@]}" || warning "Could not enable JS minification"
|
||
|
||
# Enable image optimization (WebP)
|
||
$SUDO_CMD $WP_EXECUTABLE option update litespeed.conf.media-webp 1 "${WP_RUN_ARGS[@]}" || warning "Could not enable WebP conversion"
|
||
|
||
# Enable lazy loading for images
|
||
$SUDO_CMD $WP_EXECUTABLE option update litespeed.conf.media-lazy 1 "${WP_RUN_ARGS[@]}" || warning "Could not enable lazy loading"
|
||
|
||
# Enable browser cache
|
||
$SUDO_CMD $WP_EXECUTABLE option update litespeed.conf.cache-browser 1 "${WP_RUN_ARGS[@]}" || warning "Could not enable browser cache"
|
||
|
||
success "LiteSpeed Cache basic configuration completed."
|
||
else
|
||
warning "Failed to install LiteSpeed Cache plugin. You can install it manually from the WordPress admin."
|
||
fi
|
||
|
||
# Ensure a default theme is activated (uses Twenty Twenty-Four or fallback to any available theme)
|
||
info "Ensuring a default theme is activated..."
|
||
if $SUDO_CMD $WP_EXECUTABLE theme is-installed twentytwentyfour "${WP_RUN_ARGS[@]}"; then
|
||
$SUDO_CMD $WP_EXECUTABLE theme activate twentytwentyfour "${WP_RUN_ARGS[@]}" || warning "Could not activate Twenty Twenty-Four theme"
|
||
else
|
||
# Get first available theme and activate it
|
||
FIRST_THEME=$($SUDO_CMD $WP_EXECUTABLE theme list --status=inactive --field=name --format=csv "${WP_RUN_ARGS[@]}" | head -n 1)
|
||
if [[ -n "$FIRST_THEME" ]]; then
|
||
info "Twenty Twenty-Four not found. Activating $FIRST_THEME theme instead."
|
||
$SUDO_CMD $WP_EXECUTABLE theme activate "$FIRST_THEME" "${WP_RUN_ARGS[@]}" || warning "Could not activate $FIRST_THEME theme"
|
||
fi
|
||
fi
|
||
correct_ownership_if_needed
|
||
|
||
# Create .htaccess file for WordPress permalink functionality
|
||
info "Creating .htaccess file for URL rewriting..."
|
||
if [[ ! -f ".htaccess" ]]; then
|
||
# Define the path to the .htaccess template file
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
HTACCESS_TEMPLATE="$SCRIPT_DIR/templates/htaccess.template"
|
||
|
||
# Create temporary .htaccess file
|
||
TEMP_HTACCESS=$(mktemp) || error_exit "Failed to create temporary file for .htaccess"
|
||
|
||
# Check if the template file exists and use it, otherwise use inline fallback
|
||
if [[ -f "$HTACCESS_TEMPLATE" ]]; then
|
||
info "Using external .htaccess template: $HTACCESS_TEMPLATE"
|
||
cp "$HTACCESS_TEMPLATE" "$TEMP_HTACCESS" || error_exit "Failed to copy .htaccess template to temporary file"
|
||
else
|
||
warning "External template not found at $HTACCESS_TEMPLATE, using inline template"
|
||
cat > "$TEMP_HTACCESS" <<'EOF' || error_exit "Failed to write .htaccess content to temporary file"
|
||
# BEGIN LSCACHE
|
||
## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ##
|
||
<IfModule LiteSpeed>
|
||
RewriteEngine on
|
||
CacheLookup on
|
||
RewriteRule .* - [E=Cache-Control:no-autoflush]
|
||
RewriteRule litespeed/debug/.*\.log$ - [F,L]
|
||
RewriteRule \.litespeed_conf\.dat - [F,L]
|
||
|
||
### marker ASYNC start ###
|
||
RewriteCond %{REQUEST_URI} /wp-admin/admin-ajax\.php
|
||
RewriteCond %{QUERY_STRING} action=async_litespeed
|
||
RewriteRule .* - [E=noabort:1]
|
||
### marker ASYNC end ###
|
||
|
||
### marker DROPQS start ###
|
||
CacheKeyModify -qs:fbclid
|
||
CacheKeyModify -qs:gclid
|
||
CacheKeyModify -qs:utm*
|
||
CacheKeyModify -qs:_ga
|
||
### marker DROPQS end ###
|
||
|
||
</IfModule>
|
||
## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ##
|
||
# END LSCACHE
|
||
# BEGIN NON_LSCACHE
|
||
## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ##
|
||
## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ##
|
||
# END NON_LSCACHE
|
||
|
||
# BEGIN WordPress
|
||
# The directives (lines) between "BEGIN WordPress" and "END WordPress" are
|
||
# dynamically generated, and should only be modified via WordPress filters.
|
||
# Any changes to the directives between these markers will be overwritten.
|
||
<IfModule mod_rewrite.c>
|
||
RewriteEngine On
|
||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||
RewriteBase /
|
||
RewriteRule ^index\.php$ - [L]
|
||
RewriteCond %{REQUEST_FILENAME} !-f
|
||
RewriteCond %{REQUEST_FILENAME} !-d
|
||
RewriteRule . /index.php [L]
|
||
</IfModule>
|
||
|
||
# END WordPress
|
||
EOF
|
||
fi
|
||
|
||
# Move the temporary file to the final location with proper ownership
|
||
sudo mv "$TEMP_HTACCESS" ".htaccess" || error_exit "Failed to move .htaccess file to final location"
|
||
|
||
# Set appropriate permissions and ownership for .htaccess
|
||
sudo chmod 644 .htaccess || error_exit "Failed to set permissions on .htaccess"
|
||
sudo chown "${WEB_USER}:${WEB_GROUP}" .htaccess || error_exit "Failed to set ownership on .htaccess"
|
||
|
||
# Verify the ownership was set correctly
|
||
if [[ "$(stat -c '%U:%G' .htaccess)" == "${WEB_USER}:${WEB_GROUP}" ]]; then
|
||
success ".htaccess file created and configured for LiteSpeed with proper ownership (${WEB_USER}:${WEB_GROUP})."
|
||
else
|
||
warning ".htaccess file created but ownership verification failed. Current ownership: $(stat -c '%U:%G' .htaccess)"
|
||
fi
|
||
else
|
||
info ".htaccess file already exists. Skipping creation."
|
||
fi
|
||
|
||
# Set up pretty permalinks using WP-CLI
|
||
info "Configuring WordPress permalink structure..."
|
||
$SUDO_CMD $WP_EXECUTABLE rewrite structure '/%postname%/' "${WP_RUN_ARGS[@]}" || warning "Could not set permalink structure"
|
||
$SUDO_CMD $WP_EXECUTABLE rewrite flush "${WP_RUN_ARGS[@]}" || warning "Could not flush rewrite rules"
|
||
correct_ownership_if_needed
|
||
|
||
# WP-CLI operations above might have recreated or modified .htaccess as the user executing WP-CLI.
|
||
# To enforce consistent ownership, reset it to the designated web user/group.
|
||
if [[ -f ".htaccess" ]]; then
|
||
sudo chown "${WEB_USER}:${WEB_GROUP}" .htaccess || warning "Failed to reset ownership on .htaccess after WP-CLI operations."
|
||
fi
|
||
|
||
success "WordPress installed successfully via WP-CLI."
|
||
|
||
# ------------------------------------------------------------------
|
||
# Final check: ensure .htaccess has the WordPress rewrite directives
|
||
# Some plugins (e.g., LiteSpeed Cache) may recreate .htaccess and drop
|
||
# the default WordPress block. If it's missing, append it now.
|
||
# ------------------------------------------------------------------
|
||
if [[ -f ".htaccess" ]] && ! grep -q "# BEGIN WordPress" .htaccess; then
|
||
warning ".htaccess is missing WordPress rewrite rules – adding them."
|
||
|
||
# Re-establish the path to the template in case this block is far
|
||
# from the earlier variable scope.
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
HTACCESS_TEMPLATE="$SCRIPT_DIR/templates/htaccess.template"
|
||
|
||
WORDPRESS_BLOCK=""
|
||
if [[ -f "$HTACCESS_TEMPLATE" ]]; then
|
||
WORDPRESS_BLOCK=$(awk '/# BEGIN WordPress/{flag=1} flag{print} /# END WordPress/{flag=0}' "$HTACCESS_TEMPLATE")
|
||
fi
|
||
|
||
# Fallback inline snippet if template missing or awk failed
|
||
if [[ -z "$WORDPRESS_BLOCK" ]]; then
|
||
read -r -d '' WORDPRESS_BLOCK <<'WPBLOCK' || true
|
||
# BEGIN WordPress
|
||
# The directives (lines) between "BEGIN WordPress" and "END WordPress" are
|
||
# dynamically generated, and should only be modified via WordPress filters.
|
||
# Any changes to the directives between these markers will be overwritten.
|
||
<IfModule mod_rewrite.c>
|
||
RewriteEngine On
|
||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||
RewriteBase /
|
||
RewriteRule ^index\.php$ - [L]
|
||
RewriteCond %{REQUEST_FILENAME} !-f
|
||
RewriteCond %{REQUEST_FILENAME} !-d
|
||
RewriteRule . /index.php [L]
|
||
</IfModule>
|
||
|
||
# END WordPress
|
||
WPBLOCK
|
||
fi
|
||
|
||
# Append the block to .htaccess
|
||
printf "%s\n" "$WORDPRESS_BLOCK" | sudo tee -a .htaccess > /dev/null || warning "Failed to append WordPress rules to .htaccess"
|
||
sudo chown "${WEB_USER}:${WEB_GROUP}" .htaccess || warning "Failed to set ownership after appending WordPress rules."
|
||
fi
|
||
else
|
||
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
|
||
|
||
# --- Let's Encrypt SSL Certificate Setup ---
|
||
info "Setting up Let's Encrypt SSL certificate..."
|
||
|
||
# Validate domain is properly set before proceeding
|
||
if [[ -z "$DOMAIN" ]]; then
|
||
error_exit "Domain variable is empty. Cannot proceed with SSL certificate generation."
|
||
fi
|
||
if [[ "$DOMAIN" == "localhost" || "$DOMAIN" == "localdomain" ]]; then
|
||
warning "Domain is '$DOMAIN' which is not suitable for SSL certificates. Skipping SSL setup."
|
||
info "You can manually configure SSL later or re-run with --domain=your-actual-domain.com"
|
||
# Skip SSL section entirely
|
||
SSL_SKIPPED=true
|
||
else
|
||
info "Using domain for SSL certificate: $DOMAIN"
|
||
SSL_SKIPPED=false
|
||
fi
|
||
|
||
# Only proceed with SSL setup if domain is valid
|
||
if [[ "$SSL_SKIPPED" != "true" ]]; then
|
||
# Install certbot if not present
|
||
if ! command_exists certbot; then
|
||
info "Installing certbot for Let's Encrypt certificate management..."
|
||
if command_exists apt-get; then
|
||
# Debian/Ubuntu
|
||
sudo apt-get update -qq
|
||
sudo apt-get install -y certbot python3-certbot-apache || error_exit "Failed to install certbot via apt-get"
|
||
elif command_exists yum; then
|
||
# CentOS/RHEL 7
|
||
sudo yum install -y epel-release
|
||
sudo yum install -y certbot python3-certbot-apache || error_exit "Failed to install certbot via yum"
|
||
elif command_exists dnf; then
|
||
# CentOS/RHEL 8+/Fedora
|
||
sudo dnf install -y certbot python3-certbot-apache || error_exit "Failed to install certbot via dnf"
|
||
else
|
||
warning "Package manager not detected. Please install certbot manually."
|
||
info "You can install certbot using: wget https://dl.eff.org/certbot-auto && chmod a+x certbot-auto"
|
||
fi
|
||
else
|
||
success "Certbot is already installed."
|
||
fi
|
||
|
||
# Generate SSL certificate
|
||
if command_exists certbot; then
|
||
info "Generating Let's Encrypt SSL certificate for domain: $DOMAIN"
|
||
|
||
# Create a simple verification file for webroot authentication
|
||
WEBROOT_PATH="$WP_ROOT"
|
||
ACME_CHALLENGE_DIR="$WEBROOT_PATH/.well-known/acme-challenge"
|
||
sudo mkdir -p "$ACME_CHALLENGE_DIR"
|
||
sudo chown -R "${WEB_USER}:${WEB_GROUP}" "$WEBROOT_PATH/.well-known"
|
||
sudo chmod -R 755 "$WEBROOT_PATH/.well-known"
|
||
|
||
# Try webroot method first (non-interactive)
|
||
info "Attempting SSL certificate generation using webroot method..."
|
||
# Check if certificate already exists
|
||
if [[ -f "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" ]]; then
|
||
info "SSL certificate already exists for $DOMAIN. Checking if renewal is needed..."
|
||
if sudo certbot renew --cert-name="$DOMAIN" --dry-run 2>/dev/null; then
|
||
info "Existing SSL certificate is valid and not due for renewal."
|
||
SSL_SUCCESS=true
|
||
else
|
||
info "Existing certificate needs renewal. Attempting to renew..."
|
||
if sudo certbot renew --cert-name="$DOMAIN" --force-renewal 2>/dev/null; then
|
||
SSL_SUCCESS=true
|
||
else
|
||
warning "Failed to renew existing SSL certificate."
|
||
SSL_SUCCESS=false
|
||
fi
|
||
fi
|
||
else
|
||
# Generate new certificate
|
||
if sudo certbot certonly \
|
||
--webroot \
|
||
--webroot-path="$WEBROOT_PATH" \
|
||
--email="$WP_ADMIN_EMAIL" \
|
||
--agree-tos \
|
||
--non-interactive \
|
||
--domains="$DOMAIN"; then
|
||
SSL_SUCCESS=true
|
||
else
|
||
SSL_SUCCESS=false
|
||
fi
|
||
fi
|
||
|
||
if [[ "$SSL_SUCCESS" == "true" ]]; then
|
||
success "SSL certificate is ready for $DOMAIN"
|
||
|
||
# Set up automatic renewal
|
||
info "Setting up automatic SSL certificate renewal..."
|
||
|
||
# Create renewal cron job if it doesn't exist
|
||
CRON_JOB="0 12 * * * /usr/bin/certbot renew --quiet --post-hook \"systemctl reload lshttpd || systemctl reload apache2 || systemctl reload nginx\""
|
||
if ! sudo crontab -l 2>/dev/null | grep -q "certbot renew"; then
|
||
if (sudo crontab -l 2>/dev/null; echo "$CRON_JOB") | sudo crontab - 2>/dev/null; then
|
||
success "Automatic SSL renewal configured (daily check at 12:00 PM)"
|
||
else
|
||
warning "Failed to configure automatic SSL renewal cron job"
|
||
fi
|
||
else
|
||
info "SSL renewal cron job already exists."
|
||
fi
|
||
|
||
# For LiteSpeed, we need to restart the service to pick up new certificates
|
||
info "Restarting LiteSpeed web server to apply SSL certificate..."
|
||
LITESPEED_RESTARTED=false
|
||
if sudo systemctl is-active lshttpd &>/dev/null; then
|
||
if sudo systemctl restart lshttpd 2>/dev/null; then
|
||
success "LiteSpeed (lshttpd) restarted successfully"
|
||
LITESPEED_RESTARTED=true
|
||
else
|
||
warning "Failed to restart lshttpd service"
|
||
fi
|
||
elif sudo systemctl is-active litespeed &>/dev/null; then
|
||
if sudo systemctl restart litespeed 2>/dev/null; then
|
||
success "LiteSpeed (litespeed) restarted successfully"
|
||
LITESPEED_RESTARTED=true
|
||
else
|
||
warning "Failed to restart litespeed service"
|
||
fi
|
||
else
|
||
warning "LiteSpeed service not detected or not running."
|
||
fi
|
||
|
||
if [[ "$LITESPEED_RESTARTED" != "true" ]]; then
|
||
warning "LiteSpeed service restart failed or not attempted. You may need to manually configure SSL in LiteSpeed admin panel."
|
||
info "SSL certificate location: /etc/letsencrypt/live/$DOMAIN/"
|
||
info "Certificate file: /etc/letsencrypt/live/$DOMAIN/fullchain.pem"
|
||
info "Private key file: /etc/letsencrypt/live/$DOMAIN/privkey.pem"
|
||
fi
|
||
|
||
else
|
||
warning "SSL certificate generation failed. You can manually run:"
|
||
warning "sudo certbot --webroot -w '$WP_ROOT' -d '$DOMAIN' --email '$WP_ADMIN_EMAIL' --agree-tos"
|
||
info "Or configure SSL manually in your web server control panel."
|
||
fi
|
||
else
|
||
warning "Certbot not available. SSL certificate not generated."
|
||
info "Please install certbot manually and run: sudo certbot --webroot -w '$WP_ROOT' -d '$DOMAIN'"
|
||
fi
|
||
else
|
||
info "SSL certificate setup skipped due to invalid domain."
|
||
fi
|
||
|
||
# --- Final Summary ---
|
||
success "WordPress setup process completed!"
|
||
|
||
# --- Final Ownership Correction ---
|
||
info "Performing final ownership correction to ensure all files are owned by ${WEB_USER}:${WEB_GROUP}..."
|
||
cd "$WP_ROOT" || error_exit "Failed to change directory to $WP_ROOT for final ownership correction"
|
||
|
||
# Comprehensive ownership fix for all WordPress files and directories
|
||
if ! sudo chown -R "${WEB_USER}:${WEB_GROUP}" "$WP_ROOT"; then
|
||
warning "Failed to set ownership on some files during final correction. Some files may still be owned by root."
|
||
else
|
||
success "Final ownership correction completed successfully."
|
||
fi
|
||
|
||
# Specifically ensure critical files are properly owned
|
||
critical_files=("wp-config.php" ".htaccess" "index.php")
|
||
for file in "${critical_files[@]}"; do
|
||
if [[ -f "$file" ]]; then
|
||
sudo chown "${WEB_USER}:${WEB_GROUP}" "$file" || warning "Failed to set ownership on $file"
|
||
fi
|
||
done
|
||
|
||
# Ensure LiteSpeed Cache directories have correct ownership if they exist
|
||
if [[ -d "wp-content/litespeed" ]]; then
|
||
info "Correcting LiteSpeed Cache directory ownership..."
|
||
sudo chown -R "${WEB_USER}:${WEB_GROUP}" "wp-content/litespeed" || warning "Failed to set ownership on LiteSpeed Cache directory"
|
||
fi
|
||
|
||
# Ensure uploads directory has correct ownership if it exists
|
||
if [[ -d "wp-content/uploads" ]]; then
|
||
info "Correcting uploads directory ownership..."
|
||
sudo chown -R "${WEB_USER}:${WEB_GROUP}" "wp-content/uploads" || warning "Failed to set ownership on uploads directory"
|
||
fi
|
||
|
||
# Final verification - show file ownership status
|
||
info "Verifying file ownership in WordPress root directory..."
|
||
if command_exists ls; then
|
||
info "Current file ownership in $WP_ROOT:"
|
||
ls -la "$WP_ROOT" | head -20 # Show first 20 files
|
||
|
||
# Count files with wrong ownership
|
||
wrong_ownership_count=$(find "$WP_ROOT" -maxdepth 2 \( ! -user "$WEB_USER" -o ! -group "$WEB_GROUP" \) -type f 2>/dev/null | wc -l)
|
||
if [[ "$wrong_ownership_count" -gt 0 ]]; then
|
||
warning "$wrong_ownership_count files still have incorrect ownership. You may need to run: sudo chown -R ${WEB_USER}:${WEB_GROUP} $WP_ROOT"
|
||
else
|
||
success "All files now have correct ownership (${WEB_USER}:${WEB_GROUP})"
|
||
fi
|
||
fi
|
||
|
||
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"
|
||
printf "\n"
|
||
printf "${YELLOW}LiteSpeed Cache Plugin:${NC}\n"
|
||
printf " Status: ${GREEN}Installed and Activated${NC}\n"
|
||
printf " Cache Enabled: ${GREEN}Yes${NC}\n"
|
||
printf " Cache TTL: ${GREEN}1 week (604800 seconds)${NC}\n"
|
||
printf " Optimizations: ${GREEN}CSS/JS minification, WebP, Lazy loading${NC}\n"
|
||
printf " Admin Panel: ${GREEN}https://%s/wp-admin/admin.php?page=litespeed${NC}\n" "$DOMAIN"
|
||
if [[ "$PERFORM_DB_ROOT_RESET" == "true" ]]; then
|
||
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 "%s\n" "---------------------------"
|
||
|
||
# Explicitly call cleanup before final exit (trap should also handle it)
|
||
cleanup
|
||
|
||
exit 0 |