mb-admin/scripts/install_redis.sh

391 lines
15 KiB
Bash
Raw Normal View History

#!/bin/bash
#
# 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 -uo pipefail
# NOTE: Not using set -e globally so we can handle each error explicitly
# and produce meaningful messages instead of silent exits.
# ── 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; }
# ── Constants ────────────────────────────────────────────────────────────────
REDIS_SOCKET="/var/run/redis/redis.sock"
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
# ── 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() {
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
}
# ── STEP 1: Install redis package ────────────────────────────────────────────
step_install() {
if command -v redis-server >/dev/null 2>&1; then
info "Redis binary already present: $(command -v redis-server)"
return 0
fi
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."
}
# ── STEP 2: Configure redis.conf ─────────────────────────────────────────────
step_configure() {
local conf
conf=$(get_redis_conf)
if [[ -z "$conf" ]]; then
error_exit "Cannot find redis.conf after installation. Checked: ${REDIS_CONF_PATHS[*]}. Run: rpm -ql redis | grep conf"
fi
info "Using Redis config: $conf"
# Backup (idempotent — only on first run)
if [[ ! -f "${conf}.orig" ]]; then
cp "$conf" "${conf}.orig" || error_exit "Cannot backup $conf — check disk space and permissions."
info "Backed up original config to ${conf}.orig"
fi
# 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"
# 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
# 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
# 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
# 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 at $conf"
}
# ── 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 "usermod failed to add $WEB_USER to redis group. Socket access may be restricted."
fi
}
# ── STEP 4: Enable + start Redis service ─────────────────────────────────────
step_start() {
local svc
svc=$(get_redis_svc)
if [[ -z "$svc" ]]; then
error_exit "No Redis systemd service found after installation. Check: systemctl list-unit-files | grep redis"
fi
info "Enabling $svc at boot..."
systemctl enable "$svc" 2>/dev/null || warning "Could not enable $svc at boot (non-fatal)."
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
waited=$((waited + 1))
done
if [[ ! -S "$REDIS_SOCKET" ]]; then
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
# 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 "Socket exists at $REDIS_SOCKET but PING returned '$ping_result'. Redis may have crashed."
fi
success "Redis is running and healthy (service=$svc, socket=$REDIS_SOCKET, ping=PONG)"
}
# ── 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 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."
return 0
fi
# 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 Object Cache configured successfully with Redis socket."
else
warning "Object Cache config script failed (non-fatal). Run manually:"
warning " bash $plugin_script --enable --wp-root=$WP_ROOT"
fi
}
# ════════════════════════════════════════════════════════════════════════════
# MAIN
# ════════════════════════════════════════════════════════════════════════════
# Must be root
if [[ "$(id -u)" -ne 0 ]]; then
error_exit "This script must be run as root."
fi
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"