diff --git a/mbadmin.jps b/mbadmin.jps index 8db59fb..814b366 100644 --- a/mbadmin.jps +++ b/mbadmin.jps @@ -848,7 +848,7 @@ actions: - cmd[cp]: user: root commands: - - SVC=$(systemctl list-unit-files --type=service | grep -oP '[\w@-]*redis[\w@-]*\.service' | head -1 | sed 's/\.service//'); if [ -z "$SVC" ]; then echo "Redis: NOT INSTALLED"; elif systemctl is-active --quiet "$SVC"; then echo "Redis: RUNNING (service=$SVC, socket=$(test -S /var/run/redis/redis.sock && redis-cli -s /var/run/redis/redis.sock ping 2>/dev/null || echo UNREACHABLE))"; else echo "Redis: INSTALLED but STOPPED (service=$SVC)"; fi + - bash /home/litespeed/mbmanager/scripts/install_redis.sh - return: type: info message: "${response.out}" diff --git a/scripts/install_redis.sh b/scripts/install_redis.sh index 40ec36b..83ba052 100644 --- a/scripts/install_redis.sh +++ b/scripts/install_redis.sh @@ -1,185 +1,390 @@ #!/bin/bash # -# Install and configure Redis server for WordPress on LLSMP/AlmaLinux -# - Installs redis via dnf -# - Configures Unix socket at /var/run/redis/redis.sock -# - Sets litespeed user as member of redis group for socket access -# - Enables and starts redis service -# - Configures LiteSpeed Cache Plugin Object Cache via WP-CLI +# install_redis.sh +# Install and configure Redis server for WordPress on LLSMP/AlmaLinux. +# +# Usage: +# bash install_redis.sh -- full install+configure flow +# bash install_redis.sh --status -- status check only (no install) +# +# Flow: +# 1. Check if Redis is already installed/running -> report and exit early if healthy +# 2. Install redis package via dnf (idempotent) +# 3. Locate and patch redis.conf for Unix socket +# 4. Grant litespeed user group access to the socket +# 5. Enable + start the service, wait for socket readiness +# 6. Verify PING via socket +# 7. Configure WordPress LiteSpeed Cache Plugin Object Cache # -set -euo pipefail +set -uo pipefail +# NOTE: Not using set -e globally so we can handle each error explicitly +# and produce meaningful messages instead of silent exits. -# Colors +# ── Colors & helpers ───────────────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' -info() { printf "${BLUE}[INFO] %s${NC}\n" "$@"; } -success() { printf "${GREEN}[SUCCESS] %s${NC}\n" "$@"; } -warning() { printf "${YELLOW}[WARNING] %s${NC}\n" "$@"; } -error_exit(){ printf "${RED}[ERROR] %s${NC}\n" "$@" >&2; exit 1; } +info() { printf "${BLUE}[INFO] %s${NC}\n" "$*"; } +success() { printf "${GREEN}[SUCCESS] %s${NC}\n" "$*"; } +warning() { printf "${YELLOW}[WARNING] %s${NC}\n" "$*"; } +error_exit(){ printf "${RED}[ERROR] %s${NC}\n" "$*" >&2; exit 1; } +# ── Constants ──────────────────────────────────────────────────────────────── REDIS_SOCKET="/var/run/redis/redis.sock" -REDIS_CONF="/etc/redis/redis.conf" -REDIS_CONF_ALT="/etc/redis.conf" +REDIS_SOCKET_DIR="/var/run/redis" +REDIS_CONF_PATHS=("/etc/redis/redis.conf" "/etc/redis.conf" "/etc/redis6/redis.conf" "/etc/redis7/redis.conf") WP_ROOT="/var/www/webroot/ROOT" WEB_USER="litespeed" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +STATUS_ONLY=false -# ── 1. Detect active Redis conf path ──────────────────────────────────────── +# ── Parse arguments ────────────────────────────────────────────────────────── +for arg in "$@"; do + case "$arg" in + --status) STATUS_ONLY=true ;; + esac +done + +# ── Helper: detect redis service name ──────────────────────────────────────── +get_redis_svc() { + systemctl list-unit-files --type=service 2>/dev/null \ + | grep -oP '[\w@-]*redis[\w@-]*\.service' \ + | grep -v 'sentinel' \ + | head -1 \ + | sed 's/\.service//' +} + +# ── Helper: detect redis.conf path ─────────────────────────────────────────── get_redis_conf() { - if [[ -f "$REDIS_CONF" ]]; then - echo "$REDIS_CONF" - elif [[ -f "$REDIS_CONF_ALT" ]]; then - echo "$REDIS_CONF_ALT" + for path in "${REDIS_CONF_PATHS[@]}"; do + if [[ -f "$path" ]]; then + echo "$path" + return 0 + fi + done + # Last resort: ask rpm + local rpm_conf + rpm_conf=$(rpm -ql redis 2>/dev/null | grep '\.conf$' | head -1) + if [[ -n "$rpm_conf" && -f "$rpm_conf" ]]; then + echo "$rpm_conf" + return 0 + fi + echo "" +} + +# ── Helper: test redis socket connectivity ─────────────────────────────────── +test_redis_socket() { + if [[ ! -S "$REDIS_SOCKET" ]]; then + return 1 + fi + local result + result=$(redis-cli -s "$REDIS_SOCKET" ping 2>/dev/null || true) + [[ "$result" == "PONG" ]] +} + +# ── STATUS CHECK ───────────────────────────────────────────────────────────── +do_status() { + local svc + svc=$(get_redis_svc) + + if [[ -z "$svc" ]]; then + echo "Redis: NOT INSTALLED" + return 1 # signal caller that install is needed + fi + + if systemctl is-active --quiet "$svc"; then + if test_redis_socket; then + success "Redis: RUNNING (service=$svc, socket=$REDIS_SOCKET, ping=PONG)" + else + warning "Redis: RUNNING (service=$svc) but socket unreachable at $REDIS_SOCKET" + fi + return 0 else - echo "" + local state + state=$(systemctl is-enabled "$svc" 2>/dev/null || echo "unknown") + warning "Redis: INSTALLED but STOPPED (service=$svc, enabled=$state)" + return 2 # signal caller: installed but stopped fi } -# ── 2. Install Redis if not present ───────────────────────────────────────── -install_redis() { +# ── STEP 1: Install redis package ──────────────────────────────────────────── +step_install() { if command -v redis-server >/dev/null 2>&1; then - info "Redis server binary already present at $(which redis-server)" + info "Redis binary already present: $(command -v redis-server)" return 0 fi - info "Installing Redis via dnf..." - dnf install -y redis || error_exit "Failed to install Redis. Check dnf connectivity." - success "Redis installed successfully." + info "Redis not found. Installing via dnf..." + + # Guard: check if dnf lock is held (another process installing) + local lock_retries=5 + while fuser /var/lib/rpm/.rpm.lock >/dev/null 2>&1; do + if [[ $lock_retries -le 0 ]]; then + error_exit "dnf/rpm lock held by another process after 25 seconds. Try again later." + fi + warning "dnf/rpm lock detected, waiting 5s... ($lock_retries retries left)" + sleep 5 + lock_retries=$((lock_retries - 1)) + done + + if ! dnf install -y redis 2>&1; then + error_exit "dnf install redis failed. Check: (1) internet connectivity, (2) dnf repo config, (3) run: dnf install -y redis" + fi + + # Verify binary appeared post-install + if ! command -v redis-server >/dev/null 2>&1; then + error_exit "dnf reported success but redis-server binary not found. Package may be broken." + fi + + success "Redis package installed successfully." } -# ── 3. Configure Redis for Unix socket ────────────────────────────────────── -configure_redis() { +# ── STEP 2: Configure redis.conf ───────────────────────────────────────────── +step_configure() { local conf conf=$(get_redis_conf) if [[ -z "$conf" ]]; then - error_exit "Redis config file not found at $REDIS_CONF or $REDIS_CONF_ALT" + error_exit "Cannot find redis.conf after installation. Checked: ${REDIS_CONF_PATHS[*]}. Run: rpm -ql redis | grep conf" fi - info "Configuring Redis at $conf..." + info "Using Redis config: $conf" - # Backup original config (idempotent - only once) + # Backup (idempotent — only on first run) if [[ ! -f "${conf}.orig" ]]; then - cp "$conf" "${conf}.orig" - info "Original config backed up to ${conf}.orig" + cp "$conf" "${conf}.orig" || error_exit "Cannot backup $conf — check disk space and permissions." + info "Backed up original config to ${conf}.orig" fi - # Disable TCP listener (bind to 127.0.0.1 only, no external exposure) - sed -i 's/^bind .*/bind 127.0.0.1/' "$conf" + # Ensure socket directory exists with correct ownership BEFORE writing config + if ! mkdir -p "$REDIS_SOCKET_DIR" 2>/dev/null; then + error_exit "Cannot create socket directory $REDIS_SOCKET_DIR" + fi + # redis user must own the socket dir so it can create the socket file + if ! chown redis:redis "$REDIS_SOCKET_DIR" 2>/dev/null; then + warning "Could not chown $REDIS_SOCKET_DIR to redis:redis — socket creation may fail." + fi + chmod 755 "$REDIS_SOCKET_DIR" - # Enable Unix socket + # bind: restrict to loopback only (security hardening) + if grep -q '^bind ' "$conf"; then + sed -i 's/^bind .*/bind 127.0.0.1/' "$conf" + else + echo "bind 127.0.0.1" >> "$conf" + fi + + # unixsocket path if grep -q '^unixsocket ' "$conf"; then sed -i "s|^unixsocket .*|unixsocket $REDIS_SOCKET|" "$conf" else echo "unixsocket $REDIS_SOCKET" >> "$conf" fi - # Set socket permissions so litespeed group can access it + # unixsocketperm: 775 so redis group members (litespeed) can r/w if grep -q '^unixsocketperm ' "$conf"; then sed -i 's/^unixsocketperm .*/unixsocketperm 775/' "$conf" else echo "unixsocketperm 775" >> "$conf" fi - # Sensible memory limit (256MB default, adjustable) + # maxmemory: only set if not already defined if ! grep -q '^maxmemory ' "$conf"; then echo "maxmemory 256mb" >> "$conf" echo "maxmemory-policy allkeys-lru" >> "$conf" + info "Set maxmemory 256mb with allkeys-lru eviction policy." fi - # Ensure socket directory exists with correct ownership - mkdir -p "$(dirname "$REDIS_SOCKET")" - chown redis:redis "$(dirname "$REDIS_SOCKET")" + # Verify config was written (sanity check) + if ! grep -q "^unixsocket $REDIS_SOCKET" "$conf"; then + error_exit "Config write verification failed — unixsocket not found in $conf after editing." + fi - success "Redis configuration applied." + success "Redis configuration applied at $conf" } -# ── 4. Grant litespeed user access to redis socket ────────────────────────── -configure_socket_access() { - info "Granting $WEB_USER user access to Redis socket group..." - if id "$WEB_USER" >/dev/null 2>&1; then - usermod -aG redis "$WEB_USER" || warning "Could not add $WEB_USER to redis group" +# ── STEP 3: Add litespeed to redis group ───────────────────────────────────── +step_socket_access() { + if ! id "$WEB_USER" >/dev/null 2>&1; then + warning "User '$WEB_USER' does not exist on this system — skipping group membership." + return 0 + fi + + if ! getent group redis >/dev/null 2>&1; then + warning "Group 'redis' does not exist yet — will retry after service start." + return 0 + fi + + if id -nG "$WEB_USER" | grep -qw redis; then + info "$WEB_USER is already a member of the redis group." + return 0 + fi + + if usermod -aG redis "$WEB_USER"; then success "$WEB_USER added to redis group." else - warning "User $WEB_USER not found, skipping group membership." + warning "usermod failed to add $WEB_USER to redis group. Socket access may be restricted." fi } -# ── 5. Enable and start Redis service ─────────────────────────────────────── -start_redis() { - # Detect actual service name (redis, redis6, redis7, etc.) +# ── STEP 4: Enable + start Redis service ───────────────────────────────────── +step_start() { local svc - svc=$(systemctl list-unit-files --type=service | grep -oP '[\w@-]*redis[\w@-]*\.service' | head -1 | sed 's/\.service//') + svc=$(get_redis_svc) if [[ -z "$svc" ]]; then - error_exit "No Redis systemd service unit found after installation. Something went wrong." + error_exit "No Redis systemd service found after installation. Check: systemctl list-unit-files | grep redis" fi - info "Enabling and starting $svc..." - systemctl enable "$svc" - systemctl restart "$svc" + info "Enabling $svc at boot..." + systemctl enable "$svc" 2>/dev/null || warning "Could not enable $svc at boot (non-fatal)." - # Wait up to 10s for socket to appear - local retries=10 - while [[ $retries -gt 0 ]]; do + info "Starting $svc..." + if ! systemctl restart "$svc" 2>/dev/null; then + local journal + journal=$(journalctl -u "$svc" -n 20 --no-pager 2>/dev/null || echo "unavailable") + error_exit "Failed to start $svc. Last 20 journal lines:\n$journal" + fi + + # Wait up to 15s for socket to appear + local waited=0 + while [[ $waited -lt 15 ]]; do if [[ -S "$REDIS_SOCKET" ]]; then break fi sleep 1 - retries=$((retries - 1)) + waited=$((waited + 1)) done if [[ ! -S "$REDIS_SOCKET" ]]; then - error_exit "Redis started but socket not found at $REDIS_SOCKET after 10 seconds. Check Redis logs: journalctl -u $svc" + local journal + journal=$(journalctl -u "$svc" -n 20 --no-pager 2>/dev/null || echo "unavailable") + error_exit "Redis started ($svc) but socket not found at $REDIS_SOCKET after 15s.\nJournal:\n$journal" fi - # Verify connectivity + # Final PING verification local ping_result ping_result=$(redis-cli -s "$REDIS_SOCKET" ping 2>/dev/null || echo "FAILED") if [[ "$ping_result" != "PONG" ]]; then - error_exit "Redis socket exists but PING failed. Check Redis logs: journalctl -u $svc" + error_exit "Socket exists at $REDIS_SOCKET but PING returned '$ping_result'. Redis may have crashed." fi - success "Redis is running and responding on $REDIS_SOCKET" + success "Redis is running and healthy (service=$svc, socket=$REDIS_SOCKET, ping=PONG)" } -# ── 6. Configure LiteSpeed Cache Plugin Object Cache ──────────────────────── -configure_wp_object_cache() { +# ── STEP 5: Re-check group membership after service starts (socket now exists) ── +step_verify_access() { + if ! id "$WEB_USER" >/dev/null 2>&1; then + return 0 + fi + + # Ensure redis group exists now (created by service start) + if ! getent group redis >/dev/null 2>&1; then + warning "redis group still not found after service start — skipping access check." + return 0 + fi + + if ! id -nG "$WEB_USER" | grep -qw redis; then + info "Re-attempting to add $WEB_USER to redis group..." + usermod -aG redis "$WEB_USER" || warning "Could not add $WEB_USER to redis group." + fi + + # Verify socket is accessible by litespeed group + local sock_perms + sock_perms=$(stat -c '%A %G' "$REDIS_SOCKET" 2>/dev/null || echo "unknown") + info "Socket permissions: $sock_perms" +} + +# ── STEP 6: Configure WordPress Object Cache ────────────────────────────────── +step_wp_object_cache() { local plugin_script="$SCRIPT_DIR/configure_litespeed_plugin_object_cache.sh" + # Guard: script exists if [[ ! -f "$plugin_script" ]]; then - warning "configure_litespeed_plugin_object_cache.sh not found at $plugin_script - skipping WP object cache config." + warning "configure_litespeed_plugin_object_cache.sh not found at $plugin_script — skipping WP config." return 0 fi + # Guard: WordPress present if [[ ! -f "$WP_ROOT/wp-config.php" ]]; then - warning "WordPress not found at $WP_ROOT - skipping WP object cache config." + warning "WordPress not found at $WP_ROOT — skipping WP object cache config." return 0 fi - info "Configuring LiteSpeed Cache Plugin Object Cache with Redis socket..." + # Guard: WP-CLI available + if ! command -v wp >/dev/null 2>&1; then + warning "WP-CLI not found — skipping WP object cache config. Install WP-CLI first." + return 0 + fi + + # Guard: LiteSpeed Cache plugin active (run as litespeed to get correct WP context) + local plugin_status + plugin_status=$(sudo -u "$WEB_USER" -s wp plugin is-active litespeed-cache --path="$WP_ROOT" 2>/dev/null && echo "active" || echo "inactive") + if [[ "$plugin_status" != "active" ]]; then + warning "LiteSpeed Cache plugin is not active in WordPress — skipping object cache config." + warning "Activate it at: WordPress Admin > Plugins > LiteSpeed Cache" + return 0 + fi + + info "Configuring WordPress LiteSpeed Cache Plugin Object Cache..." if bash "$plugin_script" --enable --connection-type=socket --redis-socket="$REDIS_SOCKET" --wp-root="$WP_ROOT"; then - success "WordPress LiteSpeed Cache Plugin Object Cache configured successfully." + success "WordPress Object Cache configured successfully with Redis socket." else - warning "Failed to configure WordPress Object Cache. You can run it manually: bash $plugin_script --enable --wp-root=$WP_ROOT" + warning "Object Cache config script failed (non-fatal). Run manually:" + warning " bash $plugin_script --enable --wp-root=$WP_ROOT" fi } -# ── Main ───────────────────────────────────────────────────────────────────── -info "======== Redis Install & Configure for WordPress (LLSMP) ========" +# ════════════════════════════════════════════════════════════════════════════ +# MAIN +# ════════════════════════════════════════════════════════════════════════════ -install_redis -configure_redis -configure_socket_access -start_redis -configure_wp_object_cache +# Must be root +if [[ "$(id -u)" -ne 0 ]]; then + error_exit "This script must be run as root." +fi -success "======== Redis setup complete ========" -info "Socket: $REDIS_SOCKET" -info "Config: $(get_redis_conf)" -info "Test: redis-cli -s $REDIS_SOCKET ping" +if [[ "$STATUS_ONLY" == "true" ]]; then + do_status + exit $? +fi + +info "════════ Redis Install & Configure for WordPress (LLSMP/AlmaLinux) ════════" + +# Run status check first — if Redis is already healthy, nothing to do +status_rc=0 +do_status || status_rc=$? + +if [[ $status_rc -eq 0 ]]; then + success "Redis is already installed and running. No action needed." + info "To reconfigure WordPress object cache, run:" + info " bash $SCRIPT_DIR/configure_litespeed_plugin_object_cache.sh --enable --wp-root=$WP_ROOT" + exit 0 +fi + +if [[ $status_rc -eq 2 ]]; then + # Installed but stopped — skip install/configure, just start it + info "Redis is installed but stopped. Attempting to start..." + step_start + step_verify_access + step_wp_object_cache + success "════════ Redis re-enabled successfully ════════" + exit 0 +fi + +# status_rc=1: not installed — full install flow +info "Redis not installed. Starting full installation..." +step_install +step_configure +step_socket_access +step_start +step_verify_access +step_wp_object_cache + +success "════════ Redis setup complete ════════" +info "Socket : $REDIS_SOCKET" +info "Config : $(get_redis_conf)" +info "Test : redis-cli -s $REDIS_SOCKET ping"