ultimate-wp-installer/ultimate-wp-installer-lite.sh

1014 lines
38 KiB
Bash
Raw Normal View History

2025-08-08 20:15:53 +03:00
#!/usr/bin/env bash
2025-08-08 20:57:31 +03:00
# ==============================================================================
2025-08-08 23:05:52 +03:00
# WOO v13 — WordPress Ultimate Operations (Self-Installing)
# Target: Ubuntu 22.04/24.04 LTS
2025-08-08 22:05:41 +03:00
# Design: Run-once bootstrap; afterwards `woo` only launches the menu.
2025-08-08 20:57:31 +03:00
# ==============================================================================
2025-08-08 20:15:53 +03:00
2025-08-08 20:57:31 +03:00
set -Eeuo pipefail
IFS=$'\n\t'
2025-08-08 20:33:36 +03:00
2025-08-08 20:57:31 +03:00
# --------------------------- Colors & Logging ---------------------------------
readonly RED='\033[0;31m'; readonly GREEN='\033[0;32m'; readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'; readonly NC='\033[0m'
2025-08-06 19:21:42 +03:00
readonly LOG_DIR="/var/log/woo-toolkit"
2025-08-08 20:57:31 +03:00
readonly LOG_FILE="${LOG_DIR}/woo-run-$(date +%Y%m%d_%H%M%S).log"
_log() {
local msg="${1:-}"
sudo mkdir -p "$LOG_DIR" >/dev/null 2>&1 || true
sudo touch "$LOG_FILE" >/dev/null 2>&1 || true
echo "[$(date '+%F %T')] $msg" | sudo tee -a "$LOG_FILE" >/dev/null || true
}
log() { echo -e "${BLUE}[$(date '+%F %T')]${NC} $1"; _log "$1"; }
success() { echo -e "${GREEN}${NC} $1"; _log "OK: $1"; }
warn() { echo -e "${YELLOW}${NC} $1"; _log "WARN: $1"; }
fail() { echo -e "${RED}${NC} $1"; _log "ERR: $1"; exit 1; }
# ------------------------------ Globals ---------------------------------------
readonly PHP_VERSION="${PHP_VERSION:-8.2}"
readonly WEBROOT="${WEBROOT:-/var/www}"
readonly MIN_RAM=2048 # MB
readonly F2B_MAXRETRY=5
readonly F2B_BANTIME="1d"
2025-08-08 21:24:18 +03:00
readonly INSTALL_DIR="/opt/woo"
readonly TARGET_SCRIPT="${INSTALL_DIR}/woo.sh"
2025-08-08 20:57:31 +03:00
readonly CONFIG_DIR="$HOME/.woo-toolkit"
readonly BACKUP_CONFIG_FILE="$CONFIG_DIR/backup.conf"
2025-08-06 19:01:20 +03:00
readonly XMLRPC_WHITELIST_FILE="/etc/nginx/conf.d/xmlrpc_whitelist.conf"
2025-08-08 20:57:31 +03:00
declare -A SITE_DATA=()
2025-08-06 19:01:20 +03:00
2025-08-08 22:31:32 +03:00
# --------------------------- MySQL Safe Wrappers ------------------------------
mysql_exec() {
local q="${1:-}"
if [[ -f "$HOME/.my.cnf" ]]; then
mysql --defaults-file="$HOME/.my.cnf" -e "$q"
else
sudo mysql -e "$q"
fi
}
mysqldump_pipe_restore() {
local src="${1:-}" dst="${2:-}"
2025-08-08 22:53:00 +03:00
[[ -n "$src" && -n "$dst" ]] || return 1
2025-08-08 22:31:32 +03:00
if [[ -f "$HOME/.my.cnf" ]]; then
mysqldump --defaults-file="$HOME/.my.cnf" "$src" | mysql --defaults-file="$HOME/.my.cnf" "$dst"
else
sudo mysqldump "$src" | sudo mysql "$dst"
fi
}
2025-08-08 20:57:31 +03:00
# ------------------------------ Error Handler ---------------------------------
error_handler() {
local line="${1:-?}" cmd="${2:-?}" status="${3:-$?}"
log "Critical error at line $line: $cmd (exit $status). Starting rollback..."
2025-08-06 19:01:20 +03:00
2025-08-08 20:57:31 +03:00
set +e
if [[ -n "${SITE_DATA[DB_NAME]:-}" ]]; then
log "Dropping DB: ${SITE_DATA[DB_NAME]}"
2025-08-08 22:31:32 +03:00
mysql_exec "DROP DATABASE IF EXISTS \`${SITE_DATA[DB_NAME]}\`;"
2025-08-08 20:57:31 +03:00
fi
if [[ -n "${SITE_DATA[DB_USER]:-}" ]]; then
log "Dropping DB user: ${SITE_DATA[DB_USER]}"
2025-08-08 22:31:32 +03:00
mysql_exec "DROP USER IF EXISTS '${SITE_DATA[DB_USER]}'@'localhost'; FLUSH PRIVILEGES;"
2025-08-08 20:57:31 +03:00
fi
if [[ -n "${SITE_DATA[SITE_DIR]:-}" ]]; then
log "Removing site dir: ${SITE_DATA[SITE_DIR]}"
sudo rm -rf "${SITE_DATA[SITE_DIR]}" >/dev/null 2>&1 || true
fi
if [[ -n "${SITE_DATA[DOMAIN]:-}" ]]; then
log "Removing Nginx/PHP config for ${SITE_DATA[DOMAIN]}"
sudo rm -f "/etc/nginx/sites-available/${SITE_DATA[DOMAIN]}" "/etc/nginx/sites-enabled/${SITE_DATA[DOMAIN]}" >/dev/null 2>&1 || true
sudo rm -f "/etc/php/${PHP_VERSION}/fpm/pool.d/${SITE_DATA[DOMAIN]}.conf" >/dev/null 2>&1 || true
fi
2025-08-05 23:42:21 +03:00
2025-08-08 20:57:31 +03:00
sudo systemctl restart nginx >/dev/null 2>&1 || true
sudo systemctl restart mariadb >/dev/null 2>&1 || true
sudo systemctl restart "php${PHP_VERSION}-fpm" >/dev/null 2>&1 || true
fail "Installation failed and rollback completed. See log: $LOG_FILE"
2025-08-08 20:33:36 +03:00
}
2025-08-08 20:57:31 +03:00
trap 'error_handler "$LINENO" "$BASH_COMMAND" "$?"' ERR
2025-08-08 20:33:36 +03:00
2025-08-08 20:57:31 +03:00
# ---------------------------- Preconditions -----------------------------------
require_sudo() {
if [[ "$(id -u)" -eq 0 ]]; then
warn "Running as root; continuing, but recommended to use 'ubuntu' with sudo."
else
sudo -n true 2>/dev/null || { log "Sudo required. Prompting..."; sudo -v || fail "Sudo not available."; }
2025-08-08 20:33:36 +03:00
fi
}
2025-08-08 20:57:31 +03:00
check_os() {
2025-08-08 20:33:36 +03:00
[[ -f /etc/os-release ]] || fail "Unsupported OS."
2025-08-08 20:15:53 +03:00
. /etc/os-release
2025-08-08 20:57:31 +03:00
dpkg --compare-versions "${VERSION_ID}" lt "22.04" && warn "Ubuntu ${VERSION_ID} < 22.04. Proceeding, but only 22.04+ is tested."
success "Ubuntu ${VERSION_ID} detected."
2025-08-08 20:15:53 +03:00
}
2025-08-08 20:57:31 +03:00
ensure_swap_if_low_ram() {
2025-08-08 20:15:53 +03:00
local ram_mb; ram_mb=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo)
2025-08-08 20:57:31 +03:00
if (( ram_mb < MIN_RAM )); then
if ! sudo swapon --show | grep -q '^/swapfile'; then
warn "Low RAM (${ram_mb}MB). Creating 2G swap..."
sudo fallocate -l 2G /swapfile 2>/dev/null || sudo dd if=/dev/zero of=/swapfile bs=1M count=2048
sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab >/dev/null
success "Swap created."
else
success "Swap already present."
2025-08-08 19:30:16 +03:00
fi
2025-08-08 20:57:31 +03:00
else
success "Sufficient RAM (${ram_mb}MB)."
2025-08-08 20:15:53 +03:00
fi
2025-08-05 21:25:52 +03:00
}
2025-08-08 21:24:18 +03:00
# ------------------------- Self-Install (permanent woo) -----------------------
self_install() {
sudo mkdir -p "$INSTALL_DIR"
if ! sudo cmp -s <(cat "$0") "$TARGET_SCRIPT" 2>/dev/null; then
cat "$0" | sudo tee "$TARGET_SCRIPT" >/dev/null
sudo chmod +x "$TARGET_SCRIPT"
success "Installed/updated core script at ${TARGET_SCRIPT}"
else
success "Core script already up-to-date at ${TARGET_SCRIPT}"
fi
2025-08-08 22:05:41 +03:00
if [[ ! -f /usr/local/bin/woo ]] || ! grep -q "/opt/woo/woo.sh menu" /usr/local/bin/woo; then
2025-08-08 21:52:06 +03:00
sudo tee /usr/local/bin/woo >/dev/null <<'EOF'
2025-08-08 21:24:18 +03:00
#!/usr/bin/env bash
2025-08-08 22:05:41 +03:00
exec bash /opt/woo/woo.sh menu
2025-08-08 21:24:18 +03:00
EOF
sudo chmod +x /usr/local/bin/woo
2025-08-08 22:05:41 +03:00
success "System-wide 'woo' command installed."
2025-08-08 21:24:18 +03:00
else
success "'woo' launcher already present."
fi
}
2025-08-08 22:40:47 +03:00
# ------------------------------ Nginx helpers ---------------------------------
configure_nginx_includes() {
if ! grep -q 'include /etc/nginx/conf.d/\*\.conf;' /etc/nginx/nginx.conf; then
sudo sed -i '/http {/a \ include /etc/nginx/conf.d/*.conf;' /etc/nginx/nginx.conf
fi
sudo tee /etc/nginx/sites-available/default >/dev/null <<'EOF'
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /var/www/html;
return 444;
}
EOF
[[ -L /etc/nginx/sites-enabled/default ]] || sudo ln -sf /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
2025-08-08 22:53:00 +03:00
sudo nginx -t && sudo systemctl reload nginx || true
2025-08-08 22:40:47 +03:00
}
ensure_nginx_cache_zone() {
local zfile="/etc/nginx/conf.d/woo-cache.conf"
if ! grep -qs "keys_zone=WORDPRESS" "$zfile" 2>/dev/null; then
sudo tee "$zfile" >/dev/null <<'EOF'
# Global cache zone for WordPress (http context)
fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=WORDPRESS:100m inactive=60m use_temp_path=off;
EOF
sudo nginx -t && sudo systemctl reload nginx || warn "Nginx reload failed after adding cache zone."
fi
}
2025-08-08 20:57:31 +03:00
# ------------------------------ Bootstrap -------------------------------------
add_ppa_once() {
local ppa="$1"
if ! grep -Rq "^deb .*$ppa" /etc/apt/sources.list.d /etc/apt/sources.list 2>/dev/null; then
sudo add-apt-repository -y "ppa:$ppa" || true
2025-08-08 20:15:53 +03:00
fi
}
2025-08-08 20:57:31 +03:00
install_dependencies() {
log "Installing core packages..."
sudo apt-get update -y
2025-08-08 21:24:18 +03:00
sudo apt-get install -y software-properties-common curl wget unzip git rsync psmisc dnsutils gnupg ca-certificates ufw || true
2025-08-08 20:57:31 +03:00
add_ppa_once "ondrej/php"
add_ppa_once "ondrej/nginx"
sudo apt-get update -y
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
nginx mariadb-server redis-server fail2ban certbot python3-certbot-nginx postfix unattended-upgrades haveged \
"php${PHP_VERSION}-fpm" "php${PHP_VERSION}-mysql" "php${PHP_VERSION}-curl" "php${PHP_VERSION}-mbstring" \
"php${PHP_VERSION}-xml" "php${PHP_VERSION}-zip" "php${PHP_VERSION}-gd" "php${PHP_VERSION}-opcache" \
"php${PHP_VERSION}-redis" "php${PHP_VERSION}-imagick" "php${PHP_VERSION}-bcmath"
if ! command -v wp >/dev/null 2>&1; then
log "Installing WP-CLI..."
curl -fsSL -o /tmp/wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x /tmp/wp-cli.phar && sudo mv /tmp/wp-cli.phar /usr/local/bin/wp
fi
2025-08-06 19:27:38 +03:00
2025-08-08 20:57:31 +03:00
echo 'APT::Periodic::Update-Package-Lists "1";' | sudo tee /etc/apt/apt.conf.d/20auto-upgrades >/dev/null
echo 'APT::Periodic::Unattended-Upgrade "1";' | sudo tee -a /etc/apt/apt.conf.d/20auto-upgrades >/dev/null
success "Dependencies installed."
}
configure_tuned_mariadb() {
log "Tuning MariaDB..."
local total_ram_kb; total_ram_kb=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
local ibp=$(( total_ram_kb / 4 ))
(( ibp > 4194304 )) && ibp=4194304
sudo tee /etc/mysql/mariadb.conf.d/99-woo-tuned.cnf >/dev/null <<EOF
2025-08-08 20:15:53 +03:00
[mysqld]
2025-08-08 20:57:31 +03:00
innodb_buffer_pool_size = ${ibp}K
innodb_log_file_size = 256M
innodb_file_per_table = 1
max_allowed_packet = 256M
2025-08-06 19:32:21 +03:00
EOF
2025-08-08 20:57:31 +03:00
sudo systemctl restart mariadb
success "MariaDB tuned."
}
secure_mysql() {
log "Securing MariaDB..."
if [[ -f "$HOME/.my.cnf" ]]; then success "MariaDB already secured."; return; fi
if sudo mysql -e "SELECT 1" >/dev/null 2>&1; then
local db_root_pass; db_root_pass="$(openssl rand -base64 32)"
sudo mysql <<EOF
ALTER USER 'root'@'localhost' IDENTIFIED BY '${db_root_pass}';
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost','127.0.0.1','::1');
DROP DATABASE IF EXISTS test;
FLUSH PRIVILEGES;
2025-08-08 20:15:53 +03:00
EOF
2025-08-08 20:57:31 +03:00
printf "[client]\nuser=root\npassword=%s\n" "$db_root_pass" > "$HOME/.my.cnf"
chmod 600 "$HOME/.my.cnf"
success "MariaDB secured; credentials written to ~/.my.cnf"
else
warn "Could not connect to MariaDB via socket; assuming already secured."
2025-08-08 20:15:53 +03:00
fi
2025-08-05 21:25:52 +03:00
}
2025-08-08 20:57:31 +03:00
harden_server() {
log "Hardening server..."
if ! sudo ufw status | grep -q "Status: active"; then
sudo ufw default deny incoming || true
sudo ufw default allow outgoing || true
sudo ufw allow OpenSSH || true
sudo ufw allow 'Nginx Full' || true
echo "y" | sudo ufw enable || true
fi
2025-08-08 20:15:53 +03:00
2025-08-08 20:57:31 +03:00
sudo tee /etc/fail2ban/jail.d/wordpress.conf >/dev/null <<EOF
2025-08-06 19:01:20 +03:00
[sshd]
2025-08-06 17:45:54 +03:00
enabled = true
2025-08-06 19:01:20 +03:00
maxretry = 3
2025-08-08 20:57:31 +03:00
bantime = ${F2B_BANTIME}
[wordpress-hard]
enabled = true
filter = wordpress-hard
logpath = /var/log/nginx/*access.log
maxretry = ${F2B_MAXRETRY}
bantime = ${F2B_BANTIME}
port = http,https
2025-08-06 19:01:20 +03:00
EOF
2025-08-08 20:33:36 +03:00
2025-08-08 20:57:31 +03:00
sudo tee /etc/fail2ban/filter.d/wordpress-hard.conf >/dev/null <<'EOF'
2025-08-06 19:16:08 +03:00
[Definition]
2025-08-08 20:57:31 +03:00
failregex = ^<HOST> - - \[.*\] "POST /wp-login\.php
^<HOST> - - \[.*\] "GET /wp-login\.php
^<HOST> - - \[.*\] "POST /xmlrpc\.php
2025-08-06 19:01:20 +03:00
ignoreregex =
2025-08-05 23:15:48 +03:00
EOF
2025-08-08 20:57:31 +03:00
sudo systemctl restart fail2ban || true
2025-08-08 20:33:36 +03:00
2025-08-08 20:57:31 +03:00
sudo sed -i -E 's@^;?\s*expose_php\s*=\s*On@expose_php = Off@' "/etc/php/${PHP_VERSION}/fpm/php.ini" || true
sudo sed -i -E 's@^;?\s*cgi\.fix_pathinfo\s*=\s*1@cgi.fix_pathinfo=0@' "/etc/php/${PHP_VERSION}/fpm/php.ini" || true
sudo systemctl restart "php${PHP_VERSION}-fpm" || true
2025-08-08 20:33:36 +03:00
2025-08-08 20:57:31 +03:00
if [[ ! -f "$XMLRPC_WHITELIST_FILE" ]]; then
sudo tee "$XMLRPC_WHITELIST_FILE" >/dev/null <<'EOF'
# Managed by WOO Toolkit
2025-08-08 20:15:53 +03:00
geo $xmlrpc_allowed {
default 0;
2025-08-08 20:57:31 +03:00
# Add IPs below as: 1.2.3.4 1;
2025-08-08 20:15:53 +03:00
}
EOF
2025-08-08 20:57:31 +03:00
fi
sudo nginx -t && sudo systemctl reload nginx || true
2025-08-08 20:15:53 +03:00
2025-08-08 20:57:31 +03:00
success "Hardening applied."
2025-08-06 21:07:08 +03:00
}
2025-08-06 17:45:54 +03:00
2025-08-08 20:57:31 +03:00
setup_alias() {
local usr_rc="$HOME/.bashrc"
2025-08-08 21:24:18 +03:00
grep -q "alias woo=" "$usr_rc" 2>/dev/null || echo "alias woo='bash ${TARGET_SCRIPT}'" >> "$usr_rc"
2025-08-08 20:57:31 +03:00
if sudo test -f /root/.bashrc; then
2025-08-08 21:24:18 +03:00
sudo grep -q "alias woo=" /root/.bashrc 2>/dev/null || echo "alias woo='bash ${TARGET_SCRIPT}'" | sudo tee -a /root/.bashrc >/dev/null
2025-08-08 21:11:42 +03:00
fi
}
2025-08-08 20:57:31 +03:00
# --------------------------- Site Operations ----------------------------------
create_php_pool() {
2025-08-08 20:15:53 +03:00
local domain="$1"
2025-08-08 20:57:31 +03:00
sudo tee "/etc/php/${PHP_VERSION}/fpm/pool.d/${domain}.conf" >/dev/null <<EOF
2025-08-06 19:01:20 +03:00
[${domain}]
user = www-data
group = www-data
listen = /run/php/php${PHP_VERSION}-${domain}.sock
listen.owner = www-data
listen.group = www-data
pm = ondemand
pm.max_children = 50
pm.process_idle_timeout = 10s
pm.max_requests = 500
slowlog = /var/log/php-fpm/${domain}-slow.log
php_admin_value[error_log] = /var/log/php-fpm/${domain}-error.log
php_admin_flag[log_errors] = on
php_value[session.save_handler] = redis
php_value[session.save_path] = "tcp://127.0.0.1:6379"
EOF
2025-08-08 20:57:31 +03:00
sudo mkdir -p /var/log/php-fpm
sudo touch "/var/log/php-fpm/${domain}-error.log" "/var/log/php-fpm/${domain}-slow.log"
sudo chown -R www-data:www-data /var/log/php-fpm || true
sudo systemctl restart "php${PHP_VERSION}-fpm"
2025-08-06 17:45:54 +03:00
}
2025-08-08 20:57:31 +03:00
configure_nginx_site() {
local domain="$1" enable_cache="${2:-false}"
local config_file="/etc/nginx/sites-available/${domain}"
2025-08-08 22:53:00 +03:00
# Ensure global cache zone exists when enabling cache
2025-08-08 22:40:47 +03:00
if [[ "$enable_cache" == "true" ]]; then ensure_nginx_cache_zone; fi
2025-08-08 20:57:31 +03:00
local cache_config=""
if [[ "$enable_cache" == "true" ]]; then
cache_config=$(cat <<'EOF'
2025-08-06 19:01:20 +03:00
set $skip_cache 0;
2025-08-06 19:21:42 +03:00
if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 1; }
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") { set $skip_cache 1; }
if ($http_cookie ~* "comment_author|wordpress_logged_in|wp-postpass") { set $skip_cache 1; }
2025-08-08 22:40:47 +03:00
# Use the global cache zone "WORDPRESS" defined in /etc/nginx/conf.d/woo-cache.conf
2025-08-08 20:57:31 +03:00
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
2025-08-06 19:01:20 +03:00
fastcgi_cache WORDPRESS;
2025-08-06 17:45:54 +03:00
fastcgi_cache_valid 200 60m;
2025-08-06 19:01:20 +03:00
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
2025-08-08 20:57:31 +03:00
EOF
2025-08-06 17:45:54 +03:00
)
2025-08-08 20:15:53 +03:00
fi
2025-08-06 17:45:54 +03:00
2025-08-08 20:57:31 +03:00
local multisite_rules=""
if sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp core is-installed --network --path="${WEBROOT}/${domain}" >/dev/null 2>&1; then
if sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config get SUBDOMAIN_INSTALL --path="${WEBROOT}/${domain}" --quiet >/dev/null 2>&1; then
multisite_rules=$(cat <<'EOF'
if (!-e $request_filename) {
rewrite /wp-admin$ $scheme://$host$uri/ permanent;
rewrite ^/([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) /$2 last;
rewrite ^/([_0-9a-zA-Z-]+/)?(.*\.php)$ /$2 last;
rewrite /. /index.php last;
}
EOF
)
else
multisite_rules=$(cat <<'EOF'
if (!-e $request_filename) {
rewrite /wp-admin$ $scheme://$host$uri/ permanent;
rewrite ^/[_0-9a-zA-Z-]+/(wp-(content|admin|includes).*) /$1 last;
rewrite ^/[_0-9a-zA-Z-]+/(.*\.php)$ /$1 last;
rewrite /. /index.php last;
2025-08-06 19:01:20 +03:00
}
2025-08-08 20:57:31 +03:00
EOF
)
fi
fi
sudo tee "$config_file" >/dev/null <<EOF
2025-08-06 19:01:20 +03:00
server {
2025-08-08 20:57:31 +03:00
listen 80;
server_name ${domain} www.${domain};
root ${WEBROOT}/${domain};
index index.php;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "upgrade-insecure-requests;" always;
${cache_config}
location / { try_files \$uri \$uri/ /index.php\$is_args\$args; }
${multisite_rules}
location ~ \.php\$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php${PHP_VERSION}-${domain}.sock;
}
location = /xmlrpc.php {
if (\$xmlrpc_allowed = 0) { return 403; }
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php${PHP_VERSION}-${domain}.sock;
}
location ~* /(?:uploads|files)/.*\.php\$ { deny all; }
location ~* \.(bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)\$ { deny all; }
location ~ /\. { deny all; }
location = /readme.html { deny all; }
location = /license.txt { deny all; }
2025-08-06 16:48:57 +03:00
}
EOF
2025-08-08 20:57:31 +03:00
[[ -L "/etc/nginx/sites-enabled/${domain}" ]] || sudo ln -s "$config_file" "/etc/nginx/sites-enabled/${domain}"
2025-08-08 22:40:47 +03:00
# Test & reload without tripping ERR trap
set +e
sudo nginx -t
local rc=$?
set -e
if (( rc == 0 )); then
sudo systemctl reload nginx
else
warn "Nginx config test failed; leaving previous config active."
return 1
fi
2025-08-08 20:15:53 +03:00
}
2025-08-08 20:57:31 +03:00
save_credentials() {
local domain="$1" admin_user="$2" admin_pass="$3" db_name="$4" db_user="$5" db_pass="$6"
mkdir -p "$HOME/woo_credentials"
local cred_file="$HOME/woo_credentials/${domain}.txt"
tee "$cred_file" >/dev/null <<EOF
############################################
# WordPress Credentials for: ${domain}
############################################
2025-08-08 20:15:53 +03:00
2025-08-08 20:57:31 +03:00
Site URL: https://${domain}
Admin URL: https://${domain}/wp-admin
Admin Username: ${admin_user}
Admin Password: ${admin_pass}
2025-08-08 20:15:53 +03:00
2025-08-08 20:57:31 +03:00
--------------------------------------------
2025-08-08 20:15:53 +03:00
2025-08-08 20:57:31 +03:00
Database Name: ${db_name}
Database User: ${db_user}
Database Pass: ${db_pass}
############################################
EOF
chmod 600 "$cred_file"
success "Credentials saved to $cred_file"
}
2025-08-08 21:11:42 +03:00
print_site_report() {
local domain="$1" admin_user="$2" admin_pass="$3" db_name="$4" db_user="$5" db_pass="$6" admin_email="$7" site_dir="$8"
local report_dir="$HOME/woo_credentials"
local report_file="${report_dir}/${domain}_report.txt"
local php_pool="/etc/php/${PHP_VERSION}/fpm/pool.d/${domain}.conf"
local nginx_vhost="/etc/nginx/sites-available/${domain}"
local cert_status="Pending"
local redis_status="Enabled"
local cache_tip="Disabled (enable via: Menu → Manage Site Caching)"
mkdir -p "$report_dir"
2025-08-08 21:24:18 +03:00
if [ -f "/etc/letsencrypt/live/${domain}/fullchain.pem" ]; then cert_status="Active"; fi
if grep -q "fastcgi_cache WORDPRESS;" "$nginx_vhost" 2>/dev/null; then cache_tip="Enabled (you can toggle from the menu)"; fi
2025-08-08 21:11:42 +03:00
local box_line="================================================================================"
tee "$report_file" >/dev/null <<EOF
$box_line
WOO Site Provision Report — ${domain}
$box_line
Site URLs:
- Frontend: https://${domain}
- Admin: https://${domain}/wp-admin
Admin Credentials:
- Username: ${admin_user}
- Password: ${admin_pass}
- Admin Email: ${admin_email}
Database:
- DB Name: ${db_name}
- DB User: ${db_user}
- DB Pass: ${db_pass}
Paths & Services:
- Webroot: ${site_dir}
- PHP-FPM Pool: ${php_pool}
- Nginx vHost: ${nginx_vhost}
- Redis Object Cache: ${redis_status}
- HTTPS (Let's Encrypt): ${cert_status}
- FastCGI Cache: ${cache_tip}
What was done:
- Created per-site DB + DB user with limited scope
- Downloaded and configured WordPress with secure salts and custom table prefix
- Set Redis object caching and sensible WP constants (SSL admin, no file editor, memory limits)
- Created isolated PHP-FPM pool/socket for ${domain}
- Wrote Nginx vhost (HTTP first; Certbot upgrades to HTTPS)
- Requested Let's Encrypt certificate (if DNS ready)
- Secured file permissions
Recommended next steps:
- Verify DNS A/AAAA records for ${domain} and www.${domain}
- If HTTPS shows "Pending": re-run cert issuance later with:
sudo certbot --nginx -d ${domain} -d www.${domain} --redirect --hsts --staple-ocsp
- Enable/disable FastCGI cache from: Menu → "Manage Site Caching"
- Create a first on-demand backup from: Menu → "Backup Management"
- (Optional) Configure scheduled backups with off-site rsync/scp in the same menu
This report is saved at:
${report_file}
$box_line
EOF
echo -e "\n${GREEN}Provision report written to:${NC} ${report_file}\n"
echo "----- COPY/PASTE CREDENTIALS -----"
echo "URL: https://${domain}"
echo "Admin: https://${domain}/wp-admin"
echo "User: ${admin_user}"
echo "Pass: ${admin_pass}"
echo "DB: ${db_name} | ${db_user} | ${db_pass}"
echo "----------------------------------"
}
2025-08-08 20:57:31 +03:00
install_standard_site() {
local domain="$1" site_dir="$2" admin_user="$3" admin_pass="$4" admin_email="$5"
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp core install \
--path="$site_dir" --url="https://${domain}" --title="${domain}" \
--admin_user="${admin_user}" --admin_password="${admin_pass}" --admin_email="${admin_email}"
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp plugin install redis-cache --activate --path="$site_dir"
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp redis enable --path="$site_dir"
}
2025-08-08 20:15:53 +03:00
2025-08-08 20:57:31 +03:00
install_multisite() {
local domain="$1" site_dir="$2" admin_user="$3" admin_pass="$4" admin_email="$5"
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config set WP_ALLOW_MULTISITE true --raw --path="$site_dir"
local subdomains_flag=""
2025-08-08 21:24:18 +03:00
if dig +short "wildcard-check.${domain}" | grep -Eq "([0-9]{1,3}\.){3}[0-9]{1,3}"; then subdomains_flag="--subdomains"; fi
2025-08-08 20:57:31 +03:00
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp core multisite-install \
--path="$site_dir" --url="https://${domain}" --title="${domain}" \
--admin_user="${admin_user}" --admin_password="${admin_pass}" --admin_email="${admin_email}" \
${subdomains_flag:+$subdomains_flag}
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp plugin install redis-cache --activate --network --path="$site_dir"
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp redis enable --path="$site_dir"
}
add_site() {
clear; echo -e "${GREEN}--- Add New WordPress Site ---${NC}\n"
local domain admin_user admin_email admin_pass site_type
read -rp "Domain (e.g., example.com): " domain
[[ -n "$domain" ]] || { warn "Domain cannot be empty."; return; }
read -rp "Admin username (avoid 'admin'): " admin_user
[[ -n "$admin_user" ]] || admin_user="siteadmin"
read -rp "Admin email: " admin_email
[[ -n "$admin_email" ]] || admin_email="admin@${domain}"
admin_pass="$(openssl rand -base64 16)"
read -rp "Install type [1=Standard, 2=Multisite] (default 1): " site_type
site_type="${site_type:-1}"
local site_dir="${WEBROOT}/${domain}"
SITE_DATA[DOMAIN]="$domain"; SITE_DATA[SITE_DIR]="$site_dir"
if [[ -d "$site_dir" ]]; then
warn "Directory exists: $site_dir — wiping for fresh install."
sudo rm -rf "$site_dir"
fi
local db_name="wp_$(echo "$domain" | tr '.' '_' | cut -c 1-20)_$(openssl rand -hex 4)"
local db_user="usr_$(openssl rand -hex 6)"
local db_pass; db_pass="$(openssl rand -base64 24)"
SITE_DATA[DB_NAME]="$db_name"; SITE_DATA[DB_USER]="$db_user"
log "Creating DB/user..."
2025-08-08 22:31:32 +03:00
mysql_exec "CREATE DATABASE \`${db_name}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql_exec "CREATE USER '${db_user}'@'localhost' IDENTIFIED BY '${db_pass}';"
mysql_exec "GRANT ALL PRIVILEGES ON \`${db_name}\`.* TO '${db_user}'@'localhost'; FLUSH PRIVILEGES;"
2025-08-06 19:47:34 +03:00
2025-08-08 20:57:31 +03:00
log "Preparing site dir: $site_dir"
sudo mkdir -p "$site_dir"
sudo chown -R www-data:www-data "$site_dir"
2025-08-05 21:25:52 +03:00
2025-08-08 20:57:31 +03:00
log "Downloading WordPress..."
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp core download --path="$site_dir" --locale=en_US
log "Generating wp-config.php..."
local table_prefix="wp_$(openssl rand -hex 3)_"
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config create \
--path="$site_dir" --dbname="${db_name}" --dbuser="${db_user}" --dbpass="${db_pass}" --dbprefix="${table_prefix}" \
--extra-php <<PHP
2025-08-08 20:15:53 +03:00
define('WP_CACHE', true);
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('FS_METHOD', 'direct');
define('FORCE_SSL_ADMIN', true);
define('DISALLOW_FILE_EDIT', true);
define('WP_AUTO_UPDATE_CORE', 'minor');
define('WP_DEBUG', false);
define('WP_MEMORY_LIMIT', '128M');
define('WP_MAX_MEMORY_LIMIT', '256M');
PHP
2025-08-08 20:57:31 +03:00
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config set WP_CACHE_KEY_SALT "'${domain}'" --path="$site_dir" --raw
2025-08-08 20:15:53 +03:00
2025-08-08 20:57:31 +03:00
if [[ "$site_type" == "2" ]]; then
install_multisite "$domain" "$site_dir" "$admin_user" "$admin_pass" "$admin_email"
2025-08-08 20:15:53 +03:00
else
2025-08-08 20:57:31 +03:00
install_standard_site "$domain" "$site_dir" "$admin_user" "$admin_pass" "$admin_email"
2025-08-08 20:15:53 +03:00
fi
2025-08-08 20:57:31 +03:00
log "Securing file perms..."
sudo find "$site_dir" -type d -exec chmod 755 {} \;
sudo find "$site_dir" -type f -exec chmod 644 {} \;
sudo chmod 600 "${site_dir}/wp-config.php"
2025-08-08 20:15:53 +03:00
2025-08-08 20:57:31 +03:00
create_php_pool "$domain"
configure_nginx_site "$domain" "false"
2025-08-08 20:15:53 +03:00
2025-08-08 20:57:31 +03:00
log "Requesting Let's Encrypt SSL..."
if ! sudo certbot --nginx --hsts --staple-ocsp --non-interactive --agree-tos -m "$admin_email" -d "$domain" -d "www.$domain" --redirect; then
warn "Certbot failed (likely DNS/propagation). Site remains on HTTP; retry later with: sudo certbot --nginx -d $domain -d www.$domain"
fi
2025-08-06 19:01:20 +03:00
2025-08-08 20:57:31 +03:00
save_credentials "$domain" "$admin_user" "$admin_pass" "$db_name" "$db_user" "$db_pass"
2025-08-08 21:11:42 +03:00
print_site_report "$domain" "$admin_user" "$admin_pass" "$db_name" "$db_user" "$db_pass" "$admin_email" "$site_dir"
2025-08-08 20:57:31 +03:00
success "Site '${domain}' installed."
SITE_DATA=()
}
2025-08-08 20:15:53 +03:00
2025-08-08 20:57:31 +03:00
remove_site() {
clear; echo -e "${RED}--- Remove WordPress Site ---${NC}\n"
local domain; domain=$(select_site) || return
warn "This will permanently delete ${domain} (files, DB, users, config)."
read -rp "Type the domain to confirm: " confirm
2025-08-08 21:52:06 +03:00
local clean_confirm clean_domain
clean_confirm="$(echo -n "$confirm" | tr -d ' \t\r\n')"
clean_domain="$(echo -n "$domain" | tr -d ' \t\r\n')"
[[ "$clean_confirm" == "$clean_domain" ]] || { warn "Confirmation mismatch. Aborted."; return; }
2025-08-08 20:57:31 +03:00
remove_site_silent "$domain"
success "Site ${domain} removed."
}
2025-08-08 20:15:53 +03:00
2025-08-08 20:57:31 +03:00
remove_site_silent() {
local domain="$1"
local site_dir="${WEBROOT}/${domain}"
2025-08-08 22:21:26 +03:00
local db_name="" db_user=""
if [[ -d "$site_dir" ]]; then
if sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config get DB_NAME --path="$site_dir" --quiet >/dev/null 2>&1; then
db_name=$(sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config get DB_NAME --path="$site_dir" --quiet)
db_user=$(sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config get DB_USER --path="$site_dir" --quiet)
elif sudo test -r "${site_dir}/wp-config.php"; then
db_name=$(sudo awk -F"'" '/DB_NAME/{print $4;exit}' "${site_dir}/wp-config.php" 2>/dev/null || echo "")
db_user=$(sudo awk -F"'" '/DB_USER/{print $4;exit}' "${site_dir}/wp-config.php" 2>/dev/null || echo "")
fi
2025-08-08 20:15:53 +03:00
fi
2025-08-08 20:57:31 +03:00
sudo rm -f "/etc/nginx/sites-available/${domain}" "/etc/nginx/sites-enabled/${domain}"
sudo rm -f "/etc/php/${PHP_VERSION}/fpm/pool.d/${domain}.conf"
if [[ -n "${db_name:-}" ]]; then
2025-08-08 22:31:32 +03:00
mysql_exec "DROP DATABASE IF EXISTS \`${db_name}\`;"
mysql_exec "DROP USER IF EXISTS '${db_user}'@'localhost'; FLUSH PRIVILEGES;"
else
warn "DB credentials not found; skipped DB cleanup for ${domain}"
2025-08-08 20:15:53 +03:00
fi
2025-08-08 20:57:31 +03:00
sudo rm -rf "$site_dir"
sudo systemctl reload nginx "php${PHP_VERSION}-fpm"
}
2025-08-08 20:15:53 +03:00
2025-08-08 20:57:31 +03:00
list_sites() {
clear; echo -e "${BLUE}--- Managed WordPress Sites ---${NC}\n"
if ls -1 "${WEBROOT}" 2>/dev/null | grep -v '^html$' >/dev/null; then
ls -1 "${WEBROOT}" | grep -v '^html$' | sed 's/^/ - /'
else
warn "No sites found."
fi
2025-08-08 20:15:53 +03:00
}
2025-08-08 22:53:00 +03:00
# ---- domain selector ----------------------------------------------------------
2025-08-08 20:57:31 +03:00
select_site() {
local arr=()
mapfile -t arr < <(ls -1 "${WEBROOT}" 2>/dev/null | grep -v '^html$' || true)
((${#arr[@]})) || { warn "No sites available."; return 1; }
2025-08-08 22:05:41 +03:00
>&2 echo "Please select a site to manage:"
2025-08-08 20:57:31 +03:00
local i
2025-08-08 22:05:41 +03:00
for i in "${!arr[@]}"; do >&2 echo " $((i+1))) ${arr[$i]}"; done
local choice
2025-08-08 22:21:26 +03:00
read -rp "Enter number or domain: " choice
for i in "${!arr[@]}"; do [[ "$choice" == "${arr[$i]}" ]] && { echo "$choice"; return 0; }; done
2025-08-08 22:05:41 +03:00
if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice>=1 && choice<=${#arr[@]} )); then
echo "${arr[$((choice-1))]}"
else
warn "Invalid selection."
return 1
fi
2025-08-08 20:15:53 +03:00
}
2025-08-08 22:53:00 +03:00
# ---- caching manager (fixed: answering 'n' no longer trips ERR) --------------
2025-08-08 20:57:31 +03:00
manage_caching() {
clear; echo -e "${BLUE}--- Manage Site Caching ---${NC}\n"
local domain; domain=$(select_site) || return
2025-08-08 20:15:53 +03:00
local conf="/etc/nginx/sites-available/${domain}"
2025-08-08 22:05:41 +03:00
if grep -q "fastcgi_cache WORDPRESS;" "$conf" 2>/dev/null; then
2025-08-08 20:57:31 +03:00
echo -e "Current: ${GREEN}ENABLED${NC}"
2025-08-08 22:53:00 +03:00
read -rp "Disable caching? (y/N): " a
if [[ "${a,,}" == "y" ]]; then
configure_nginx_site "$domain" "false"
success "Cache disabled."
else
success "No changes."
fi
2025-08-08 20:15:53 +03:00
else
2025-08-08 20:57:31 +03:00
echo -e "Current: ${RED}DISABLED${NC}"
2025-08-08 22:53:00 +03:00
read -rp "Enable caching? (y/N): " a
if [[ "${a,,}" == "y" ]]; then
configure_nginx_site "$domain" "true"
success "Cache enabled."
else
success "No changes."
fi
2025-08-08 20:15:53 +03:00
fi
}
2025-08-08 20:57:31 +03:00
manage_xmlrpc() {
clear; echo -e "${BLUE}--- Manage XML-RPC Whitelist (global) ---${NC}\n"
while true; do
echo "Whitelisted IPs:"
grep -E '^[[:space:]]*([0-9]{1,3}\.){3}[0-9]{1,3}[[:space:]]+1;' "$XMLRPC_WHITELIST_FILE" 2>/dev/null \
| awk '{print " - " $1}' || echo " - None"
echo -e "\n1) Add IP 2) Remove IP 3) Back"
read -rp "Choice: " c
2025-08-08 22:21:26 +03:00
case "$c" in
2025-08-08 20:57:31 +03:00
1)
read -rp "IP to add: " ip
[[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || { warn "Invalid IP."; continue; }
if grep -q "$ip" "$XMLRPC_WHITELIST_FILE"; then warn "Already present."
2025-08-08 20:15:53 +03:00
else
2025-08-08 20:57:31 +03:00
sudo sed -i "/^geo /a \ ${ip} 1;" "$XMLRPC_WHITELIST_FILE"
sudo nginx -t && sudo systemctl reload nginx
success "Added."
2025-08-08 20:15:53 +03:00
fi
2025-08-08 20:57:31 +03:00
;;
2)
read -rp "IP to remove: " ip
if sudo grep -q "$ip" "$XMLRPC_WHITELIST_FILE"; then
2025-08-08 22:21:26 +03:00
sudo sed -i -E "/^[[:space:]]*${ip}[[:space:]]+1;/d" "$XMLRPC_WHITELIST_FILE"
2025-08-08 20:57:31 +03:00
sudo nginx -t && sudo systemctl reload nginx
success "Removed."
else
warn "Not found."
fi
;;
3) break ;;
*) warn "Invalid." ;;
esac
done
2025-08-08 20:15:53 +03:00
}
2025-08-08 20:57:31 +03:00
create_on_demand_backup() {
local domain; domain=$(select_site) || return
local site_dir="${WEBROOT}/${domain}"
local backup_dir="$HOME/woo_backups/${domain}"
mkdir -p "$backup_dir"
local ts; ts="$(date +%Y%m%d_%H%M%S)"
local file_backup="${backup_dir}/files_${ts}.tar.gz"
local db_backup="${backup_dir}/db_${ts}.sql"
local tmp_db="/tmp/db_${domain}_${ts}.sql"
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp db export "$tmp_db" --path="$site_dir"
sudo mv "$tmp_db" "$db_backup"
sudo tar -czf "$file_backup" -C "$WEBROOT" "$domain"
success "Backup complete.\n Files: $file_backup\n DB: $db_backup"
2025-08-08 20:15:53 +03:00
}
2025-08-08 20:57:31 +03:00
configure_scheduled_backups() {
clear; echo -e "${BLUE}--- Scheduled Backups ---${NC}\n"
read -rp "Enable daily backups at 02:00? (y/N): " a
if [[ "${a,,}" != "y" ]]; then
2025-08-08 21:24:18 +03:00
(crontab -l 2>/dev/null | grep -v "${TARGET_SCRIPT} backup-all") | crontab - || true
2025-08-08 20:57:31 +03:00
success "Scheduled backups disabled."
return
fi
mkdir -p "$CONFIG_DIR"
read -rp "Enable off-site rsync/scp? (y/N): " r
if [[ "${r,,}" == "y" ]]; then
read -rp "Remote user: " RU; read -rp "Remote host: " RH; read -rp "Remote path: " RP
printf "REMOTE_USER=%s\nREMOTE_HOST=%s\nREMOTE_PATH=%s\n" "$RU" "$RH" "$RP" > "$BACKUP_CONFIG_FILE"
chmod 600 "$BACKUP_CONFIG_FILE"
warn "Ensure SSH keys are set up for passwordless transfer."
2025-08-08 20:15:53 +03:00
else
2025-08-08 20:57:31 +03:00
rm -f "$BACKUP_CONFIG_FILE" || true
2025-08-08 20:15:53 +03:00
fi
2025-08-08 20:57:31 +03:00
2025-08-08 21:24:18 +03:00
(crontab -l 2>/dev/null | grep -v "${TARGET_SCRIPT} backup-all"; echo "0 2 * * * bash ${TARGET_SCRIPT} backup-all") | crontab -
2025-08-08 20:57:31 +03:00
success "Daily backups scheduled."
}
backup_all_sites() {
log "--- All-Sites Scheduled Backup ---"
2025-08-08 22:40:47 +03:00
local s; mapfile -t s < <(ls -1 "${WEBROOT}" 2>/dev/null | grep -v '^html$' || true)
2025-08-08 20:57:31 +03:00
for domain in "${s[@]}"; do
local sd="${WEBROOT}/${domain}" bd="$HOME/woo_backups/${domain}"
mkdir -p "$bd"
local d; d="$(date +%Y%m%d)"
local tmp_db="/tmp/db_${domain}_${d}.sql"
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp db export "$tmp_db" --path="$sd"
local archive="${bd}/full_${d}.tar.gz"
sudo tar -czf "$archive" -C "$WEBROOT" "$domain" -C /tmp "$(basename "$tmp_db")"
sudo rm -f "$tmp_db"
log "Backup: $archive"
if [[ -f "$BACKUP_CONFIG_FILE" ]]; then
# shellcheck disable=SC1090
source "$BACKUP_CONFIG_FILE"
rsync -a -e ssh "$archive" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}" || warn "Off-site sync failed for ${domain}"
fi
2025-08-08 22:21:26 +03:00
ls -tp "${bd}"/full_*.tar.gz 2>/dev/null | tail -n +8 | xargs -r -d $'\n' rm -f --
2025-08-08 20:57:31 +03:00
done
log "--- Backups Done ---"
2025-08-08 20:15:53 +03:00
}
2025-08-08 20:57:31 +03:00
clone_to_staging() {
clear; echo -e "${BLUE}--- Clone Site to Staging ---${NC}\n"
local domain; domain=$(select_site) || return
local staging="staging.${domain}"
local sd="${WEBROOT}/${domain}"
local td="${WEBROOT}/${staging}"
[[ -d "$sd" ]] || { warn "Source does not exist."; return; }
[[ -d "$td" ]] && { warn "Staging exists. Removing."; remove_site_silent "$staging"; }
log "Cloning files..."
sudo cp -a "$sd" "$td"
log "Cloning DB..."
local db_name db_user
db_name=$(sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config get DB_NAME --path="$sd" --quiet)
db_user=$(sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config get DB_USER --path="$sd" --quiet)
local sdb="${db_name}_stg_$(openssl rand -hex 3)"
2025-08-08 22:31:32 +03:00
mysql_exec "CREATE DATABASE \`${sdb}\`;"
mysqldump_pipe_restore "${db_name}" "${sdb}"
mysql_exec "GRANT ALL PRIVILEGES ON \`${sdb}\`.* TO '${db_user}'@'localhost'; FLUSH PRIVILEGES;"
2025-08-08 20:57:31 +03:00
log "Pointing staging to new DB..."
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config set DB_NAME "$sdb" --path="$td"
log "Search-replace URLs..."
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp search-replace "https://${domain}" "https://${staging}" --all-tables --path="$td"
create_php_pool "$staging"
configure_nginx_site "$staging" "false"
local admin_email; admin_email=$(sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp option get admin_email --path="$sd" --quiet || echo "admin@${domain}")
if ! sudo certbot --nginx --hsts --staple-ocsp --non-interactive --agree-tos -m "$admin_email" -d "$staging" --redirect; then
warn "Staging SSL failed. Check DNS for ${staging}."
fi
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp option update blog_public 0 --path="$td"
success "Staging ready: https://${staging}"
}
site_toolkit() {
clear; echo -e "${BLUE}--- Site Toolkit ---${NC}\n"
local domain; domain=$(select_site) || return
local dir="${WEBROOT}/${domain}"
while true; do
echo -e "\n${YELLOW}${domain}${NC}"
echo "1) List users"
echo "2) Optimize DB"
echo "3) List cron events"
echo "4) Quick health (checksums, plugin status)"
echo "5) Back"
read -rp "Choice: " c
case "$c" in
1) sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp user list --path="$dir" ;;
2) sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp db optimize --path="$dir" ;;
3) sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp cron event list --path="$dir" ;;
4)
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp core verify-checksums --path="$dir" || true
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp plugin status --path="$dir" || true
;;
5) break ;;
*) warn "Invalid." ;;
esac
read -n 1 -s -r -p "Press any key..."
2025-08-08 20:15:53 +03:00
done
2025-08-08 20:57:31 +03:00
}
manage_debugging() {
clear; echo -e "${BLUE}--- Debugging ---${NC}\n"
local domain; domain=$(select_site) || return
local dir="${WEBROOT}/${domain}"
while true; do
2025-08-08 22:21:26 +03:00
local dbg_val; dbg_val=$(sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config get WP_DEBUG --path="$dir" --quiet 2>/dev/null || echo "false")
local dbg_display="OFF"; [[ "$dbg_val" == "true" ]] && dbg_display="ON"
echo "WP_DEBUG: $dbg_display"
2025-08-08 20:57:31 +03:00
echo "1) Toggle WP_DEBUG"
echo "2) Tail debug.log"
echo "3) Back"
read -rp "Choice: " c
case "$c" in
1)
2025-08-08 22:21:26 +03:00
if [[ "$dbg_val" == "true" ]]; then
2025-08-08 20:57:31 +03:00
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config set WP_DEBUG false --raw --path="$dir"
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config set WP_DEBUG_LOG false --raw --path="$dir"
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config set WP_DEBUG_DISPLAY false --raw --path="$dir"
else
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config set WP_DEBUG true --raw --path="$dir"
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config set WP_DEBUG_LOG true --raw --path="$dir"
sudo -u www-data WP_CLI_CACHE_DIR='/tmp/wp-cli-cache' wp config set WP_DEBUG_DISPLAY false --raw --path="$dir"
fi
success "Toggled."
;;
2)
log "Tailing ${dir}/wp-content/debug.log (Ctrl+C to stop)"
sudo tail -f "${dir}/wp-content/debug.log" || true
;;
3) break ;;
*) warn "Invalid." ;;
esac
2025-08-08 20:15:53 +03:00
done
2025-08-08 20:57:31 +03:00
}
2025-08-08 20:33:36 +03:00
2025-08-08 20:57:31 +03:00
manage_backups() {
clear; echo -e "${BLUE}--- Backups ---${NC}\n"
echo "1) Create On-Demand Backup"
echo "2) Configure Scheduled Backups"
echo "3) Back"
read -rp "Choice: " c
case "$c" in
1) create_on_demand_backup ;;
2) configure_scheduled_backups ;;
*) : ;;
esac
2025-08-08 20:15:53 +03:00
}
2025-08-08 20:57:31 +03:00
# ------------------------------- Menu -----------------------------------------
main_menu() {
2025-08-08 20:15:53 +03:00
while true; do
clear
2025-08-08 20:57:31 +03:00
echo -e "${BLUE}▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ WordPress Ultimate Operations (WOO) ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓${NC}"
echo " 1) List Managed Sites"
echo " 2) Add New Site (Standard/Multisite)"
echo " 3) Remove Existing Site"
echo " 4) Manage Site Caching"
echo " 5) Manage XML-RPC Whitelist"
echo " 6) Backup Management"
echo " 7) Clone Site to Staging"
echo " 8) Site Toolkit"
echo " 9) Debugging Tools"
echo "10) Setup SSH MFA (google-authenticator)"
echo "11) Exit"
read -rp "Choose: " ch
case "$ch" in
1) list_sites ;;
2) add_site ;;
3) remove_site ;;
4) manage_caching ;;
5) manage_xmlrpc ;;
6) manage_backups ;;
7) clone_to_staging ;;
8) site_toolkit ;;
9) manage_debugging ;;
10) sudo apt-get install -y libpam-google-authenticator && google-authenticator || warn "MFA setup skipped." ;;
11) echo "Bye."; exit 0 ;;
*) warn "Invalid option." ;;
esac
echo -e "\nPress any key to return to menu..."
read -n 1 -s || true
2025-08-08 20:15:53 +03:00
done
2025-08-06 20:00:38 +03:00
}
2025-08-06 19:01:20 +03:00
2025-08-08 20:57:31 +03:00
# ------------------------------- Entry ----------------------------------------
main() {
2025-08-08 21:52:06 +03:00
if [[ "${1:-}" == "menu" ]]; then
main_menu
exit 0
fi
2025-08-08 20:57:31 +03:00
if [[ "${1:-}" == "backup-all" ]]; then backup_all_sites; exit 0; fi
if [[ "${1:-}" == "remove-site-silent" && -n "${2:-}" ]]; then remove_site_silent "$2"; exit 0; fi
2025-08-08 20:33:36 +03:00
2025-08-08 20:57:31 +03:00
require_sudo
check_os
2025-08-08 22:05:41 +03:00
self_install
2025-08-08 20:57:31 +03:00
ensure_swap_if_low_ram
install_dependencies
configure_nginx_includes
2025-08-08 22:40:47 +03:00
ensure_nginx_cache_zone
2025-08-08 20:57:31 +03:00
secure_mysql
configure_tuned_mariadb
harden_server
setup_alias
2025-08-08 20:33:36 +03:00
2025-08-08 20:57:31 +03:00
success "Initial server setup complete."
2025-08-08 20:33:36 +03:00
main_menu
}
main "$@"