创建文件
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