396 lines
15 KiB
Bash
396 lines
15 KiB
Bash
#!/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 — resolve full path so sudo -u retains it
|
|
local wp_bin
|
|
for _wp in "/usr/local/bin/wp" "/usr/bin/wp" "/home/litespeed/bin/wp"; do
|
|
if [ -x "$_wp" ]; then wp_bin="$_wp"; break; fi
|
|
done
|
|
[ -z "${wp_bin:-}" ] && wp_bin=$(command -v wp 2>/dev/null || echo "")
|
|
if [ -z "${wp_bin:-}" ]; then
|
|
warning "WP-CLI not found — skipping WP object cache config. Expected at /usr/local/bin/wp"
|
|
return 0
|
|
fi
|
|
|
|
# Guard: LiteSpeed Cache plugin active (run as litespeed with full wp path)
|
|
local plugin_status
|
|
plugin_status=$(sudo -u "$WEB_USER" "$wp_bin" 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"
|