Debian一键安装LEMP+Redis+Opcache

创建文件

nano go.sh

粘贴代码

#!/usr/bin/env bash
set -euo pipefail

APP_NAME="lemp-manager"

SITES_AVAIL="/etc/nginx/sites-available"
SITES_EN="/etc/nginx/sites-enabled"
WEB_BASE="/var/www"

STATE_DIR="/etc/lemp-manager"
CACHE_DIR_BASE="/var/cache/nginx"
CACHE_DIR="${CACHE_DIR_BASE}/lemp-wp"

LEMP_GZIP_BEGIN="### lemp-manager gzip begin"
LEMP_GZIP_END="### lemp-manager gzip end"

need_root() {
  if [[ "${EUID}" -ne 0 ]]; then
    echo "请用 root 运行:sudo $0 ..."
    exit 1
  fi
}

os_check() {
  if ! command -v apt >/dev/null 2>&1; then
    echo "仅支持 Debian/Ubuntu (apt)。"
    exit 1
  fi
}

write_file_atomic() {
  local target="$1"
  local tmp="${target}.tmp.$$"
  cat > "${tmp}"
  mv -f "${tmp}" "${target}"
}

detect_php_version() {
  if command -v php >/dev/null 2>&1; then
    php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;'
  else
    echo ""
  fi
}

# 【优化】更精准的 Socket 探测,优先匹配当前 PHP 版本
detect_php_sock() {
  local php_ver
  php_ver="$(detect_php_version)"
  
  # 优先寻找带版本号的 socket (例如 php8.2-fpm.sock)
  if [[ -n "${php_ver}" ]] && [[ -S "/run/php/php${php_ver}-fpm.sock" ]]; then
    echo "/run/php/php${php_ver}-fpm.sock"
    return 0
  fi

  # 备选方案
  local sock=""
  sock="$(ls -1 /run/php/php*-fpm.sock 2>/dev/null | head -n 1 || true)"
  [[ -n "${sock}" ]] && echo "${sock}" && return 0
  return 1
}

safe_nginx_test_reload() {
  nginx -t
  systemctl reload nginx
}

ensure_dirs() {
  mkdir -p "${SITES_AVAIL}" "${SITES_EN}" "${WEB_BASE}" "${STATE_DIR}"
}

ask_password_visible() {
  local prompt="$1"
  local __varname="$2"
  local pw=""

  while true; do
    read -rp "${prompt}" pw
    if [[ -z "${pw}" ]]; then
      echo "密码不能为空,请重输。"
      continue
    fi
    echo "你输入的密码是:${pw}"
    read -rp "确认使用该密码?[y/N]: " ok
    ok="${ok:-N}"
    if [[ "${ok}" =~ ^[Yy]$ ]]; then
      printf -v "${__varname}" "%s" "${pw}"
      return 0
    fi
  done
}

ensure_gzip_on_once() {
  local nginx_conf="/etc/nginx/nginx.conf"
  if grep -R --line-number --fixed-strings "gzip on;" /etc/nginx 2>/dev/null | grep -vq "/etc/nginx/conf.d/01-gzip.conf"; then
    return 0
  fi
  if grep -qF "${LEMP_GZIP_BEGIN}" "${nginx_conf}" 2>/dev/null; then
    return 0
  fi
  local tmp="${nginx_conf}.tmp.$$"
  awk -v BEGIN_MARK="${LEMP_GZIP_BEGIN}" -v END_MARK="${LEMP_GZIP_END}" '
    $0 ~ /^[[:space:]]*http[[:space:]]*\{/ && inserted==0 {
      print $0
      print "    " BEGIN_MARK
      print "    gzip on;"
      print "    " END_MARK
      inserted=1
      next
    }
    { print $0 }
    END {
      if (inserted==0) exit 2
    }
  ' "${nginx_conf}" > "${tmp}" || {
    rm -f "${tmp}"
    echo "警告:未能自动在 nginx.conf 的 http{} 中插入 gzip on;。请手动处理。"
    return 0
  }
  mv -f "${tmp}" "${nginx_conf}"
}

pick_cache_zone_name() {
  local base="LEMP_WP"
  local z="${base}"
  local i=0
  while true; do
    if grep -R --line-number -E "keys_zone=${z}:" /etc/nginx 2>/dev/null | grep -vq "/etc/nginx/conf.d/00-lemp-wp-cache.conf"; then
      i=$((i+1))
      z="${base}_${i}"
      continue
    fi
    echo "${z}" > "${STATE_DIR}/cache_zone_name"
    return 0
  done
}

get_cache_zone_name() {
  if [[ -f "${STATE_DIR}/cache_zone_name" ]]; then
    cat "${STATE_DIR}/cache_zone_name"
  else
    echo "LEMP_WP"
  fi
}

configure_nginx_global() {
  mkdir -p "${CACHE_DIR}"
  chown -R www-data:www-data "${CACHE_DIR_BASE}"
  pick_cache_zone_name
  local zone
  zone="$(get_cache_zone_name)"

  write_file_atomic /etc/nginx/conf.d/00-lemp-wp-cache.conf <<EOF
fastcgi_cache_path ${CACHE_DIR} levels=1:2 keys_zone=${zone}:100m inactive=60m;
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;
EOF

  write_file_atomic /etc/nginx/conf.d/01-gzip.conf <<'EOF'
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
EOF
  ensure_gzip_on_once
}

configure_opcache() {
  local php_ver="$1"
  local ini="/etc/php/${php_ver}/fpm/conf.d/99-custom-opcache.ini"
  write_file_atomic "${ini}" <<'EOF'
[opcache]
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.save_comments=1
EOF
}

install_lemp() {
  ensure_dirs
  apt update
  apt -y install nginx mariadb-server redis-server \
    php-fpm php-mysql php-redis php-opcache php-curl php-gd php-mbstring php-xml php-zip php-bcmath \
    curl ca-certificates unzip certbot python3-certbot-nginx

  systemctl enable --now nginx
  systemctl enable --now mariadb
  systemctl enable --now redis-server

  local php_ver
  php_ver="$(detect_php_version)"
  if [[ -n "${php_ver}" ]]; then
    configure_opcache "${php_ver}"
    systemctl enable --now "php${php_ver}-fpm" || true
    systemctl reload "php${php_ver}-fpm" || true
  fi

  if [[ -e "${SITES_EN}/default" ]]; then
    rm -f "${SITES_EN}/default"
  fi
  configure_nginx_global
  safe_nginx_test_reload
  echo "==> 安装完成"
}

create_db_admin() {
  echo "==> 创建一个具备 *.* 全权限的数据库账号"
  read -rp "输入数据库用户名(例如 dbadmin): " DBUSER
  [[ -z "${DBUSER}" ]] && echo "用户名不能为空" && exit 1
  local DBPASS=""
  ask_password_visible "输入该用户密码(可见): " DBPASS
  mariadb <<SQL
CREATE USER IF NOT EXISTS '${DBUSER}'@'localhost' IDENTIFIED BY '${DBPASS}';
GRANT ALL PRIVILEGES ON *.* TO '${DBUSER}'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;
SQL
  echo "==> 已创建:${DBUSER}@localhost"
}

create_site_db_user() {
  local dbname="$1"
  local dbuser="$2"
  local dbpass="$3"
  mariadb <<SQL
CREATE DATABASE IF NOT EXISTS \`${dbname}\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS '${dbuser}'@'localhost' IDENTIFIED BY '${dbpass}';
GRANT ALL PRIVILEGES ON \`${dbname}\`.* TO '${dbuser}'@'localhost';
FLUSH PRIVILEGES;
SQL
}

add_domain() {
  ensure_dirs

  # 1. 询问主域名
  read -rp "输入主域名(例如 example.com): " DOMAIN
  [[ -z "${DOMAIN}" ]] && echo "域名不能为空" && exit 1

  # 2. 【新增】询问别名 (www)
  local DEFAULT_ALIAS="www.${DOMAIN}"
  local ALIAS_DOMAIN=""
  read -rp "输入附加域名(直接回车默认为 ${DEFAULT_ALIAS},输入 'none' 跳过): " ALIAS_INPUT
  if [[ "${ALIAS_INPUT}" == "none" ]]; then
    ALIAS_DOMAIN=""
  elif [[ -z "${ALIAS_INPUT}" ]]; then
    ALIAS_DOMAIN="${DEFAULT_ALIAS}"
  else
    ALIAS_DOMAIN="${ALIAS_INPUT}"
  fi

  local ROOT="${WEB_BASE}/${DOMAIN}/public"
  mkdir -p "${ROOT}"
  chown -R www-data:www-data "${WEB_BASE}/${DOMAIN}"

  local PHP_SOCK
  PHP_SOCK="$(detect_php_sock || true)"
  [[ -z "${PHP_SOCK}" ]] && echo "未找到 PHP-FPM socket,请确认已安装并启动 php-fpm。" && exit 1

  local zone
  zone="$(get_cache_zone_name)"

  # 数据库设置
  read -rp "是否为该站点创建 WordPress 数据库与用户?[y/N]: " DO_DB
  DO_DB="${DO_DB:-N}"
  if [[ "${DO_DB}" =~ ^[Yy]$ ]]; then
    local DBNAME DBUSER DBPASS
    # 自动净化数据库名,防止特殊字符报错
    local SAFE_DB_NAME="wp_${DOMAIN//./_}"
    SAFE_DB_NAME="${SAFE_DB_NAME:0:63}" # MySQL 数据库名限制长度
    
    read -rp "数据库名(回车默认 ${SAFE_DB_NAME}): " INPUT_DBNAME
    DBNAME="${INPUT_DBNAME:-${SAFE_DB_NAME}}"
    
    read -rp "数据库用户(例如 wpuser_${DOMAIN%%.*}): " DBUSER
    [[ -z "${DBUSER}" ]] && echo "数据库用户不能为空" && exit 1
    DBPASS=""
    ask_password_visible "数据库密码(可见): " DBPASS

    create_site_db_user "${DBNAME}" "${DBUSER}" "${DBPASS}"
    echo "==> 数据库已创建:DB=${DBNAME}, USER=${DBUSER}"
  fi

  # 【优化】根据域名生成唯一的 Redis 前缀
  local REDIS_PREFIX="wp_${DOMAIN//./_}_"
  # 生成一个 Redis 推荐配置文件,供用户复制
  cat > "${WEB_BASE}/${DOMAIN}/redis-config-help.txt" <<EOF
【重要】为防止 Redis 冲突,请在安装 WordPress 后,
将以下内容复制到您的 wp-config.php 文件中(放在 'stop editing' 之前):

define( 'WP_REDIS_PREFIX', '${REDIS_PREFIX}' );
define( 'WP_REDIS_DATABASE', 0 );
define( 'WP_REDIS_TIMEOUT', 1 );
define( 'WP_REDIS_READ_TIMEOUT', 1 );
EOF

  # 准备 server_name 字符串
  local SERVER_NAMES="${DOMAIN}"
  if [[ -n "${ALIAS_DOMAIN}" ]]; then
    SERVER_NAMES="${DOMAIN} ${ALIAS_DOMAIN}"
  fi

  # 生成 Nginx 配置
  cat > "${SITES_AVAIL}/${DOMAIN}" <<EOF
server {
    listen 80;
    listen [::]:80;
    server_name ${SERVER_NAMES};

    root ${ROOT};
    index index.php index.html;

    access_log off;
    # 建议开启错误日志以便排查
    error_log /var/log/nginx/${DOMAIN}.error.log error;

    location ^~ /.well-known/acme-challenge/ { allow all; }

    location / {
        try_files \$uri \$uri/ /index.php?\$args;
    }

    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff2?)\$ {
        expires 365d;
        access_log off;
        add_header Cache-Control "public";
    }

    # WordPress 微缓存控制
    set \$skip_cache 0;
    if (\$request_method = POST) { set \$skip_cache 1; }
    if (\$query_string != "") { set \$skip_cache 1; }
    if (\$http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") { set \$skip_cache 1; }
    if (\$request_uri ~* "^/wp-admin/|^/wp-login\\.php") { set \$skip_cache 1; }

    location ~ \\.php\$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:${PHP_SOCK};

        # 【关键】缓存 Key 必须包含 host 否则多站点会串
        fastcgi_cache_key "\$scheme\$request_method\$host\$request_uri";
        
        fastcgi_cache ${zone};
        fastcgi_cache_valid 200 301 302 2m;
        fastcgi_cache_use_stale error timeout invalid_header http_500;
        fastcgi_cache_lock on;

        fastcgi_cache_bypass \$skip_cache;
        fastcgi_no_cache \$skip_cache;

        add_header X-FastCGI-Cache \$upstream_cache_status;
    }

    location ~ /\\. { deny all; }
}
EOF

  ln -sf "${SITES_AVAIL}/${DOMAIN}" "${SITES_EN}/${DOMAIN}"
  safe_nginx_test_reload

  echo "==> 站点配置已创建:${SERVER_NAMES}"
  echo "==> Web 根目录:${ROOT}"

  read -rp "是否立刻申请并安装 SSL(Let’s Encrypt)?[y/N]: " DO_SSL
  DO_SSL="${DO_SSL:-N}"
  if [[ "${DO_SSL}" =~ ^[Yy]$ ]]; then
    read -rp "输入邮箱(Let’s Encrypt 用于到期/安全通知): " EMAIL
    [[ -z "${EMAIL}" ]] && echo "邮箱不能为空" && exit 1
    
    # 组装 Certbot 参数
    local CERTBOT_DOMAINS="-d ${DOMAIN}"
    if [[ -n "${ALIAS_DOMAIN}" ]]; then
      CERTBOT_DOMAINS="${CERTBOT_DOMAINS} -d ${ALIAS_DOMAIN}"
    fi
    
    # shellcheck disable=SC2086
    certbot --nginx ${CERTBOT_DOMAINS} -m "${EMAIL}" --agree-tos --non-interactive --redirect
    echo "==> SSL 已配置完成:${SERVER_NAMES}"
  fi
  
  echo "--------------------------------------------------------"
  echo "【非常重要】防止 Redis 冲突步骤:"
  echo "在该站点安装完 WordPress 后,请务必编辑 wp-config.php"
  echo "并添加以下代码(已为您保存到 ${WEB_BASE}/${DOMAIN}/redis-config-help.txt):"
  echo ""
  echo "define( 'WP_REDIS_PREFIX', '${REDIS_PREFIX}' );"
  echo "--------------------------------------------------------"
}

delete_domain() {
  read -rp "输入要删除的主域名: " DOMAIN
  [[ -z "${DOMAIN}" ]] && echo "域名不能为空" && exit 1

  rm -f "${SITES_EN:?}/${DOMAIN}"
  rm -f "${SITES_AVAIL:?}/${DOMAIN}"

  safe_nginx_test_reload

  read -rp "是否删除证书(certbot delete)?[y/N]: " DEL_CERT
  DEL_CERT="${DEL_CERT:-N}"
  if [[ "${DEL_CERT}" =~ ^[Yy]$ ]]; then
    certbot -n delete --cert-name "${DOMAIN}" || true
    systemctl reload nginx || true
  fi

  read -rp "是否删除站点目录 ${WEB_BASE}/${DOMAIN} ?[y/N]: " DEL_DIR
  DEL_DIR="${DEL_DIR:-N}"
  if [[ "${DEL_DIR}" =~ ^[Yy]$ ]]; then
    rm -rf "${WEB_BASE:?}/${DOMAIN}"
  fi

  echo "==> 已删除:${DOMAIN}"
}

list_domains() {
  echo "==> 已启用站点配置:"
  ls -1 "${SITES_EN}" 2>/dev/null | sed '/^default$/d' || true
}

usage() {
  cat <<EOF
用法:
  sudo ./${APP_NAME} install        # 安装 LEMP 环境
  sudo ./${APP_NAME} add            # 添加域名(支持自动添加 www,生成 Redis 防冲突配置)
  sudo ./${APP_NAME} del            # 删除域名
  sudo ./${APP_NAME} list           # 列出域名
  sudo ./${APP_NAME} dbadmin        # 创建全权限数据库账号
EOF
}

main() {
  need_root
  os_check

  local cmd="${1:-}"
  case "${cmd}" in
    install) install_lemp ;;
    dbadmin) create_db_admin ;;
    add) add_domain ;;
    del) delete_domain ;;
    list) list_domains ;;
    ""|help|-h|--help) usage ;;
    *) echo "未知命令:${cmd}"; usage; exit 1 ;;
  esac
}

main "$@"

保存后赋予权限

chmod +x go.sh
./go.sh install

给redis设置最大内存为64mb

#!/bin/bash

# 1. 设置最大内存为 64MB
sed -i 's/^#\?\s*maxmemory .*/maxmemory 64mb/' /etc/redis/redis.conf

# 2. 设置淘汰策略为 "allkeys-lru"
# 含义:当内存满了,自动删除最久没使用的数据,腾出空间给新数据。
# 这样你的 Redis 永远不会报错,只会默默循环利用这 64MB 空间。
sed -i 's/^#\?\s*maxmemory-policy .*/maxmemory-policy allkeys-lru/' /etc/redis/redis.conf

# 3. 重启 Redis 使配置生效
systemctl restart redis

# 4. 验证结果
echo ">> 正在验证 Redis 内存配置..."
redis-cli config get maxmemory
redis-cli config get maxmemory-policy

 

滚动至顶部