feat: redis_status auto-installs+configures Redis when not found - full sad-path coverage

main
Anthony 2026-02-28 15:56:05 +08:00
parent 9d1e35e803
commit 3c2d2fbbe7
2 changed files with 288 additions and 83 deletions

View File

@ -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}"

View File

@ -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"
else
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
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 ========"
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"