#!/bin/bash
export PATH=$PATH:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin

# Check if user is root
if [ $(id -u) != "0" ]; then
    echo "Error: You must be root to run this script!"
    exit 1
else
    if env |grep -q SUDO; then
        acme_sh_sudo="-f"
    fi
fi

echo "+-------------------------------------------+"
echo "|    NextLNMP 服务器管理工具 by 静水流深    |"
echo "+-------------------------------------------+"
echo "|          https://nextlnmp.cn              |"
echo "+-------------------------------------------+"

PHPFPMPIDFILE=/usr/local/php/var/run/php-fpm.pid

# ===== 栈参数（公共函数据此适配，改这里不要改 common.sh）=====
Stack_Web='nginx'
Vhost_Dir='/usr/local/nginx/conf/vhost'
Web_SSL_Dir='/usr/local/nginx/conf/ssl'
Web_Initd='/etc/init.d/nginx'

arg1=$1
arg2=$2


# ===== NextLNMP CLI 公共函数库（由 tools/build-cli.sh 拼装，勿直接编辑生成物）=====

Check_DB()
{
    if [[ -s /usr/local/mariadb/bin/mysql && -s /usr/local/mariadb/bin/mysqld_safe && -s /etc/my.cnf ]]; then
        MySQL_Bin="/usr/local/mariadb/bin/mysql"
        MySQL_Ver=`/usr/local/mariadb/bin/mysql_config --version`
    elif [[ -s /usr/local/mysql/bin/mysql && -s /usr/local/mysql/bin/mysqld_safe && -s /etc/my.cnf ]]; then
        MySQL_Bin="/usr/local/mysql/bin/mysql"
        MySQL_Ver=`/usr/local/mysql/bin/mysql_config --version`
    else
        MySQL_Bin="None"
    fi
}

Make_TempMycnf()
{
    cat >~/.my.cnf<<EOF
[client]
user=root
password='$1'
EOF
    chmod 600 ~/.my.cnf
}

# 这些临时文件里带明文数据库密码，绝不能放 /tmp：
#   ① /tmp 世界可写，固定文件名可被普通用户抢先建成符号链接，root 一写就被劫持；
#   ② 每个站点的 open_basedir 恰好放行了 /tmp/，任何一个被挂马的 PHP 站点
#      都能在建站/删站的窗口里读到新数据库的账号密码。
# 改用 root 私有目录（/root 本身 700，不存在抢建问题）。
NLX_TMPDIR="/root/.nextlnmp-tmp"
Nlx_Tmp_Init()
{
    [ -d "${NLX_TMPDIR}" ] || mkdir -p "${NLX_TMPDIR}"
    chmod 700 "${NLX_TMPDIR}"
}

Do_Query()
{
    Nlx_Tmp_Init
    echo "$1" >${NLX_TMPDIR}/.mysql.tmp
    chmod 600 ${NLX_TMPDIR}/.mysql.tmp
    Check_DB
    ${MySQL_Bin} --defaults-file=~/.my.cnf <${NLX_TMPDIR}/.mysql.tmp
    return $?
}

TempMycnf_Clean()
{
    if [ -s ~/.my.cnf ]; then
        rm -f ~/.my.cnf
    fi
    if [ -s ${NLX_TMPDIR}/.mysql.tmp ]; then
        rm -f ${NLX_TMPDIR}/.mysql.tmp
    fi
}

# 网站根目录是用户手输的，之前既不校验也不加引号，直接拿去
# chmod -R / chown -R。两种翻车方式：
#  ① 填成父目录（提示里默认值是 /home/wwwroot/域名，很容易顺手删成
#     /home/wwwroot），于是机器上【其它所有站点】的文件被递归改成 www:www 755
#     ——原本 600 的 wp-config.php、.env、备份 sql 全变成同机其它站点可读，
#     没有确认步骤，也没法撤销。
#  ② 路径含空格，未加引号导致分词，mkdir 建出两个目录、chown 作用在错误的树上、
#     `cat >${vhostdir}/.user.ini` 直接 ambiguous redirect ——
#     结果是 open_basedir 隔离静默缺席，而用户以为建好了。
Check_VHost_Dir()
{
    local d="$1"
    case "${d}" in
        ''|*[![:print:]]*)
            Echo_Red "网站目录无效。"; return 1 ;;
        /*) ;;
        *)  Echo_Red "网站目录必须是绝对路径（以 / 开头），当前输入：${d}"; return 1 ;;
    esac
    # 去掉结尾多余的 /，便于和黑名单比对
    while [ "${d}" != "/" ] && [ "${d%/}" != "${d}" ]; do d="${d%/}"; done
    case "${d}" in
        /|/bin|/boot|/dev|/etc|/home|/lib|/lib64|/opt|/proc|/root|/run|/sbin|/srv|/sys|/tmp|/usr|/var)
            Echo_Red "拒绝把 ${d} 当作网站根目录——接下来会对它执行 chmod -R / chown -R，"
            Echo_Red "那会递归改掉这个目录下所有文件的权限和属主。"
            return 1 ;;
    esac
    if [ "${d}" = "${Default_Website_Dir%/}" ] || [ "${d}" = "/home/wwwroot" ]; then
        Echo_Red "拒绝把站点根目录设成 ${d}——那是所有站点的父目录，"
        Echo_Red "chmod -R / chown -R 会波及这台机器上的每一个站点。"
        Echo_Red "请用它下面的子目录，例如 ${d}/${domain}"
        return 1 ;
    fi
    case "${d}" in
        *[[:space:]]*)
            Echo_Red "网站目录不要包含空格：${d}"
            return 1 ;;
    esac
    return 0
}

Enter_Database_Name()
{
    while :;do
        Echo_Yellow "Enter database name: "
        read database_name || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
        if [ "${database_name}" == "" ]; then
            Echo_Red "Database Name can't be empty!"
        else
            break
        fi
    done
}

Enter_Ftp_Name()
{
    while :;do
        Echo_Yellow "Enter ftp account name: "
        read ftp_account_name || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
        if [ "${ftp_account_name}" == "" ]; then
            Echo_Red "FTP account name can't be empty!"
        else
            break
        fi
    done
}

Add_Ftp_Menu()
{
    Enter_Ftp_Name
    while :;do
        Echo_Yellow "Enter password for ftp account ${ftp_account_name}: "
        read ftp_account_password || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
        if [ "${ftp_account_password}" == "" ]; then
            Echo_Red "FTP password can't be empty!"
        else
            break
        fi
    done
    if [ "${vhostdir}" == "" ]; then
        while :;do
            Echo_Yellow "Enter directory for ftp account ${ftp_account_name}: "
            read vhostdir || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
            if [ "${vhostdir}" == "" ]; then
                Echo_Red "Directory can't be empty!"
            else
                break
            fi
        done
    fi
}

Check_Pureftpd()
{
    if [ ! -f /usr/local/pureftpd/sbin/pure-ftpd ]; then
        Echo_Red "Pureftpd was not installed!"
        exit 1
    fi
}

Show_Ftp()
{
    List_Ftp
    Enter_Ftp_Name
    echo "Your ftp account ${ftp_account_name} details:"
    /usr/local/pureftpd/bin/pure-pw show ${ftp_account_name}
    [ $? -eq 0 ] && echo "Ok." || echo "failed."
}

Add_DNS_SSL_Select_Menu()
{
    echo "1: Use Let's Encrypt to create SSL Certificate and Key"
    echo "2: Use ZeroSSL to create SSL Certificate and Key"
        while :;do
        Echo_Yellow "Enter 1 or 2: "
        read dns_ssl_choice || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
        if [[ "${dns_ssl_choice}" =~ ^(1|2)$ ]]; then
            Check_Acme_EMail
            break
        else
            Echo_Red "Please Enter 1 or 2!"
        fi
    done
}

Add_DNS_SSL_Only_Info_Menu()
{
    Add_DNS_SSL_Select_Menu

    domain=""
    while :;do
        Echo_Yellow "Please enter domain(example: nextlnmp.com): "
        read domain || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
        if [ "${domain}" != "" ] && [[ "$domain" = "${domain%[[:space:]]*}" ]]; then
            echo " Your domain: ${domain}"
            break
        else
            Echo_Red "Domain name can't be empty or contain spaces!"
        fi
    done

    Echo_Yellow "Enter more domain name(example: *.nextlnmp.com): "
    read moredomain
    if [ "${moredomain}" != "" ]; then
        echo " domain list: ${moredomain}"
    fi
}

Color_Text()
{
  echo -e " \e[0;$2m$1\e[0m"
}

Echo_Red()
{
  echo $(Color_Text "$1" "31")
}

Echo_Green()
{
  echo $(Color_Text "$1" "32")
}

Echo_Yellow()
{
  echo -n $(Color_Text "$1" "33")
}

Echo_Blue()
{
  echo $(Color_Text "$1" "34")
}

Sleep_Sec()
{
    seconds=$1
    while [ "${seconds}" -ge "0" ];do
      echo -ne "\r     \r"
      echo -n ${seconds}
      seconds=$(($seconds - 1))
      sleep 1
    done
    echo -ne "\r"
}

Add_Database()
{
    Add_DB_Result=""      # 每次调用先清空，别让上一次的结果残留下来
    # MySQL 8.x（含 8.4）已删除 GRANT ... IDENTIFIED BY 旧语法；MariaDB 版本号不以 8 开头，不受影响
    if echo "${MySQL_Ver}" | grep -Eqi '^8\.';then
        Nlx_Tmp_Init
        cat >${NLX_TMPDIR}/.add_mysql.sql<<EOF
CREATE USER '${database_name}'@'localhost' IDENTIFIED BY '${mysql_password}';
CREATE USER '${database_name}'@'127.0.0.1' IDENTIFIED BY '${mysql_password}';
GRANT USAGE ON *.* TO '${database_name}'@'localhost';
GRANT USAGE ON *.* TO '${database_name}'@'127.0.0.1';
CREATE DATABASE IF NOT EXISTS \`${database_name}\`;
GRANT ALL PRIVILEGES ON \`${database_name}\`.* TO '${database_name}'@'localhost';
GRANT ALL PRIVILEGES ON \`${database_name}\`.* TO '${database_name}'@'127.0.0.1';
FLUSH PRIVILEGES;
EOF

    else
        Nlx_Tmp_Init
        cat >${NLX_TMPDIR}/.add_mysql.sql<<EOF
CREATE USER '${database_name}'@'localhost' IDENTIFIED BY '${mysql_password}';
CREATE USER '${database_name}'@'127.0.0.1' IDENTIFIED BY '${mysql_password}';
GRANT USAGE ON *.* TO '${database_name}'@'localhost' IDENTIFIED BY '${mysql_password}';
GRANT USAGE ON *.* TO '${database_name}'@'127.0.0.1' IDENTIFIED BY '${mysql_password}';
CREATE DATABASE IF NOT EXISTS \`${database_name}\`;
GRANT ALL PRIVILEGES ON \`${database_name}\`.* TO '${database_name}'@'localhost';
GRANT ALL PRIVILEGES ON \`${database_name}\`.* TO '${database_name}'@'127.0.0.1';
FLUSH PRIVILEGES;
EOF

    fi
    ${MySQL_Bin} --defaults-file=~/.my.cnf < ${NLX_TMPDIR}/.add_mysql.sql
    # 把结果记下来。原来只 echo 一行就完事，收尾面板照样按用户当初的 y/n
    # 打印"数据库名/用户/密码"三行——库根本没建，用户却拿着这套凭据去装 WordPress。
    # 最典型的触发：两个长域名截断后自动库名同名，第二次建站 CREATE USER 报错，
    # mysql 客户端没带 --force 会在第一条错误处整体中止。
    if [ $? -eq 0 ]; then
        echo "Add database Successfully."
        Add_DB_Result="ok"
    else
        Echo_Red "Add database failed!"
        Add_DB_Result="fail"
    fi
    rm -f ${NLX_TMPDIR}/.add_mysql.sql
}

Add_Ftp()
{
    Add_FTP_Result=""      # 每次调用先清空，别让上一次的结果残留下来
    www_uid=`id -u www`
    www_gid=`id -g www`
    if [ ! -d "${vhostdir}" ]; then
        mkdir -p ${vhostdir}
        chown www:www -R ${vhostdir}
    fi
    cat >/tmp/pass${ftp_account_name}<<EOF
${ftp_account_password}
${ftp_account_password}
EOF
    /usr/local/pureftpd/bin/pure-pw useradd ${ftp_account_name} -f /usr/local/pureftpd/etc/pureftpd.passwd -u ${www_uid} -g ${www_gid} -d ${vhostdir} -m < /tmp/pass${ftp_account_name}
    if [ $? -eq 0 ]; then
        echo "Created FTP User: ${ftp_account_name} Successfully."
        Add_FTP_Result="ok"
    else
        Echo_Red "FTP User: ${ftp_account_name} already exists!"
        Add_FTP_Result="fail"
    fi
    rm -f /tmp/pass${ftp_account_name}
}

Edit_Database()
{
    while :;do
        Echo_Yellow "Enter database username: "
        read database_username || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
        if [ "${database_username}" == "" ]; then
            Echo_Red "Database Username can't be empty!"
        else
            break
        fi
    done
    while :;do
        Echo_Yellow "Enter NEW Password: "
        read database_username_passwd || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
        if [ "${database_username_passwd}" == "" ]; then
            Echo_Red "Database Password can't be empty!"
        else
            break
        fi
    done

    if echo "${MySQL_Ver}" | grep -Eq '^(10\.([4-9]|[1-9][0-9])|1[1-9])\.'; then
        # MariaDB 10.4+ 的 mysql.user 是视图，直接 UPDATE 会失败，走 SET PASSWORD
        Do_Query "SET PASSWORD FOR '${database_username}'@'127.0.0.1' = PASSWORD('${database_username_passwd}');"
        Do_Query "SET PASSWORD FOR '${database_username}'@'localhost' = PASSWORD('${database_username_passwd}');"
    elif echo "${MySQL_Ver}" | grep -Eqi '^5.7.';then
        Do_Query "UPDATE mysql.user SET authentication_string=PASSWORD('${database_username_passwd}') WHERE User='${database_username}' AND Host IN ('localhost', '127.0.0.1', '::1');"
    elif echo "${MySQL_Ver}" | grep -Eqi '^8\.';then
        Do_Query "SET PASSWORD FOR '${database_username}'@'127.0.0.1' = '${database_username_passwd}';"
        Do_Query "SET PASSWORD FOR '${database_username}'@'localhost' = '${database_username_passwd}';"
    else
        Do_Query "UPDATE mysql.user SET Password=PASSWORD('${database_username_passwd}') WHERE User='${database_username}' AND Host IN ('localhost', '127.0.0.1', '::1');"
    fi
    [ $? -eq 0 ] && echo "Edit user password Successfully." || echo "Edit user password databases failed!"
    Do_Query "FLUSH PRIVILEGES;"
}

List_Database()
{
    ${MySQL_Bin} --defaults-file=~/.my.cnf -e "SHOW DATABASES;"
    [ $? -eq 0 ] && echo "List all databases Successfully." || echo "List all databases failed!"
}

List_Ftp()
{
    /usr/local/pureftpd/bin/pure-pw list -f /usr/local/pureftpd/etc/pureftpd.passwd
    [ $? -eq 0 ] && echo "List FTP User Successfully." || echo "Read database failed."
}

Del_Database()
{
    List_Database
    Enter_Database_Name
    if [[ "${database_name}" == "information_schema" || "${database_name}" == "mysql" || "${database_name}" == "performance_schema" ]]; then
        echo "MySQL System Database can't be delete!"
        exit 1
    fi
    echo "Your will delete database and MySQL user with same name: ${database_name}"
    echo "Sleep 10s, Press ctrl+c to cancel..."
    Sleep_Sec 10
    Nlx_Tmp_Init
    cat >${NLX_TMPDIR}/.del.mysql.sql<<EOF
DROP USER '${database_name}'@'127.0.0.1';
DROP USER '${database_name}'@'localhost';
DROP DATABASE \`${database_name}\`;
FLUSH PRIVILEGES;
EOF
    ${MySQL_Bin} --defaults-file=~/.my.cnf < ${NLX_TMPDIR}/.del.mysql.sql
    [ $? -eq 0 ] && echo "Delete database: ${database_name} Successfully." || echo "Delete database: ${database_name} failed!"
    rm -f ${NLX_TMPDIR}/.del.mysql.sql
}

Edit_Ftp()
{
    List_Ftp
    Enter_Ftp_Name
    Echo_Yellow "Enter password for ftp account ${ftp_account_name}: "
    read ftp_account_password
    if [ "${ftp_account_password}" != "" ]; then
        cat >/tmp/pass${ftp_account_name}<<EOF
${ftp_account_password}
${ftp_account_password}
EOF
        /usr/local/pureftpd/bin/pure-pw passwd ${ftp_account_name} -f /usr/local/pureftpd/etc/pureftpd.passwd -m < /tmp/pass${ftp_account_name}
        [ $? -eq 0 ] && echo "FTP User: ${ftp_account_name} change password Successfully." || echo "FTP User: ${ftp_account_name} change password failed!"
        rm -f /tmp/pass${ftp_account_name}
    else
        echo "FTP password will not change."
    fi
    Echo_Yellow "Enter directory for ftp account ${ftp_account_name}: "
    read vhostdir
    if [ "${vhostdir}" != "" ]; then
        www_uid=`id -u www`
        www_gid=`id -g www`
        if [ ! -d "${vhostdir}" ]; then
            mkdir -p ${vhostdir}
            chown www:www -R ${vhostdir}
        fi
        /usr/local/pureftpd/bin/pure-pw usermod ${ftp_account_name} -f /usr/local/pureftpd/etc/pureftpd.passwd -u ${www_uid} -g ${www_gid} -d ${vhostdir} -m
        [ $? -eq 0 ] && echo "FTP User: ${ftp_account_name} change directory Successfully." || echo "FTP User: ${ftp_account_name} change directory failed!"
    else
        echo "Directory will not change."
    fi
}

Del_Ftp()
{
    List_Ftp
    Enter_Ftp_Name
    echo "Your will delete ftp user ${ftp_account_name}"
    echo "Sleep 3s,Press ctrl+c to cancel..."
    Sleep_Sec 3
    /usr/local/pureftpd/bin/pure-pw userdel ${ftp_account_name} -f /usr/local/pureftpd/etc/pureftpd.passwd -m
    [ $? -eq 0 ] && echo "FTP User: ${ftp_account_name} deleted Successfully." || echo "FTP User: ${ftp_account_name} not exists!"
}

Function_Ftp()
{
    case "$1" in
        [aA][dD][dD])
            Add_Ftp_Menu
            Add_Ftp
            ;;
        [lL][iI][sS][tT])
            List_Ftp
            ;;
        [dD][eE][lL])
            Del_Ftp
            ;;
        [eE][dD][iI][tT])
            Edit_Ftp
            ;;
        [eE][xX][iI][tT])
            exit 1
            ;;
        [sS][hH][oO][wW])
            Show_Ftp
            ;;
        *)
            echo "Usage: nextlnmp ftp {add|list|del}"
            exit 1
            ;;
    esac
}

Function_Vhost()
{
    case "$1" in
        [aA][dD][dD])
            Add_VHost
            ;;
        [lL][iI][sS][tT])
            List_VHost
            ;;
        [dD][eE][lL])
            Del_VHost
            ;;
        [eE][xX][iI][tT])
            exit 1
            ;;
        *)
            echo "Usage: nextlnmp vhost {add|list|del}"
            exit 1
            ;;
    esac
}

Function_Database()
{
    case "$1" in
        [aA][dD][dD])
            Add_Database_Menu
            Add_Database
            ;;
        [lL][iI][sS][tT])
            List_Database
            ;;
        [dD][eE][lL])
            Del_Database
            ;;
        [eE][dD][iI][tT])
            Edit_Database
            ;;
        [eE][xX][iI][tT])
            exit 1
            ;;
        *)
            echo "Usage: nextlnmp database {add|list|del}"
            exit 1
            ;;
    esac
}

Check_Acme_EMail()
{
    if [ ! -s /usr/local/acme.sh/account.conf ] || ! cat /usr/local/acme.sh/account.conf | grep -Eq "^ACCOUNT_EMAIL="; then
        default_email="letsencrypt@nextlnmp.cn"
        echo ""
        Echo_Yellow "请输入接收 SSL 证书到期提醒的邮箱："
        echo "（直接回车使用默认：${default_email}，证书到期通知将发至 NextLNMP 官方）"
        read -e email_address
        if [ "${email_address}" == "" ]; then
            email_address="${default_email}"
        fi
        while :;do
            if [[ "${email_address}" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
                echo "使用邮箱：${email_address}"
                break
            else
                Echo_Red "邮箱格式不正确，请重新输入："
                read -e email_address || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
            fi
        done
    fi
}

Return_301_Menu()
{
    echo ""
    Echo_Yellow "是否将 HTTP 自动跳转到 HTTPS？（推荐开启，直接回车默认开启）[Y/n] "
    read -e using_301
    if [[ "${using_301}" == "n" || "${using_301}" == "N" ]]; then
        echo "不设置 301 跳转。"
        using_301="n"
    else
        echo ""
        echo "已设置：http://${domain} 自动跳转至 https://${domain}"
        using_301="y"
    fi
}

# 「连不上数据库」和「密码错了」是两回事。原来一律报「密码错误，请重新输入！」
# 并无限重问：数据库没启动的用户输什么都不对，也不会被告知该去启动服务；
# 加上 read 不判 EOF，非交互场景下就是 100% CPU 的死循环。
DB_Conn_Failed()
{
    # 用 . 代替撇号，避开 shell 引号地狱（Can't / Cannot 都能覆盖）
    echo "$1" | grep -Eqi "Can.t connect|Cannot connect|connect to (local )?server|\b(2002|2003)\b|No such file or directory"
}

Verify_DB_Password()
{
    Check_DB
    if [ "${MySQL_Bin}" = "None" ]; then
        Echo_Red "没有检测到已安装的 MySQL/MariaDB，无法验证密码。"
        exit 1
    fi
    if [ -f /root/.nextlnmp_db_password ]; then
        DB_Root_Password=$(cat /root/.nextlnmp_db_password)
        Make_TempMycnf "${DB_Root_Password}"
        Do_Query "" >/dev/null 2>&1
        if [ $? -eq 0 ]; then
            echo "已自动读取数据库 root 密码。"
            return
        fi
    fi
    status=1
    while [ $status -eq 1 ]; do
        Echo_Yellow "请输入数据库 root 密码："
        read -e DB_Root_Password || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
        Make_TempMycnf "${DB_Root_Password}"
        db_err=$(Do_Query "" 2>&1 >/dev/null)
        status=$?
        if [ $status -ne 0 ]; then
            if DB_Conn_Failed "${db_err}"; then
                Echo_Red "连不上数据库服务，这不是密码问题："
                Echo_Red "  ${db_err}"
                Echo_Red "请先确认数据库已启动（nextlnmp restart，或 /etc/init.d/mysql start），再重试。"
                exit 1
            fi
            Echo_Red "密码错误，请重新输入！"
        fi
    done
    echo "数据库 root 密码验证通过。"
}

Add_Database_Menu()
{
    # 由域名派生库名；nextlnmp database add 独立调用时没有 ${domain}，退回随机名，
    # 否则提示里承诺的"自动生成"会是空串，用户回车反被拒绝（真机装测实测）
    auto_db_name=$(echo "${domain}" | sed 's/\.//g' | sed 's/-//g' | cut -c1-16)
    if [ -z "${auto_db_name}" ]; then
        auto_db_name="db$(< /dev/urandom tr -dc 'a-z0-9' | head -c8)"
    fi
    echo ""
    echo "请输入数据库名（直接回车使用自动生成的：${auto_db_name}）："
    read -e database_name || database_name=""
    if [ "${database_name}" == "" ]; then
        database_name="${auto_db_name}"
    fi
    while [ "${database_name}" == "" ]; do
        Echo_Red "数据库名不能为空，请重新输入："
        # read 失败=标准输入已关闭（管道/脚本调用），此时必须退出循环，
        # 否则空转烧 CPU（真机实测 35% CPU 停不下来）
        read -e database_name || { database_name="${auto_db_name}"; break; }
    done
    echo "数据库名：${database_name}"
    auto_password=$(< /dev/urandom tr -dc 'A-Za-z0-9' | head -c16)
    echo ""
    echo "请输入数据库密码（直接回车自动生成随机密码）："
    read -e mysql_password || mysql_password=""
    if [ "${mysql_password}" == "" ]; then
        mysql_password="${auto_password}"
    fi
    Echo_Green "数据库密码：${mysql_password}"
}

List_VHost()
{
    echo "当前虚拟主机列表："
    ls ${Vhost_Dir}/ | grep ".conf$" | sed 's/.conf//g'
}

Add_SSL()
{
    if [ "${ssl_choice}" == "1" ]; then
        # 「自有证书」这条分支原来直接调 Create_SSL_Config，而后者是 `cat >>`
        # 追加。配置文件不存在时（LNMPA/LAMP 的 `nextlnmp ssl add` 是主路径，
        # 用户手输域名、还没 vhost add 过，或者域名打错一个字母），追加就等于
        # 新建：整个文件里只有一个 443 server、一个 80 server 都没有。
        # 接着默认开启的 301 以"文件里第一个 access_log"为锚点插入跳转，
        # 而那个 access_log 此时就在 443 块里 —— HTTPS 请求被 301 到自己，
        # 浏览器直接 ERR_TOO_MANY_REDIRECTS，站点整个打不开。
        # 分支 2-4 早就有这道补建，只有这条漏了。
        if [ ! -s "${Vhost_Dir}/${domain}.conf" ]; then
            if [ -z "${vhostdir}" ]; then
                Echo_Red "还没有 ${domain} 的站点配置，也没有网站根目录信息。"
                Echo_Red "请先用 nextlnmp vhost add 建站，再来配置 SSL。"
                exit 1
            fi
            Echo_Yellow "未找到 ${domain} 的站点配置，先补建 HTTP 站点再配置 SSL。"
            Add_VHost_Config
        fi
        Create_SSL_Config
    elif echo "${ssl_choice}" | grep -Eqi "^[2-4]$"; then
        letsdomain=""
        if [ "${moredomain}" != "" ]; then
            letsdomain="-d ${domain}"
            for i in ${moredomain};do
                letsdomain=${letsdomain}" -d ${i}"
            done
        else
            letsdomain="-d ${domain}"
        fi
        if [ ! -s "${Vhost_Dir}/${domain}.conf" ]; then
            Add_VHost_Config
        fi
        if [ ! -d "${vhostdir}" ]; then
            mkdir -p "${vhostdir}"
        fi

        if [[ "${vhostdir}" == "" || "${letsdomain}" == "" ]]; then
            Echo_Red "Two parameters are needed!"
            exit 1
        fi
        if [ ! -d "${vhostdir}" ]; then
            Echo_Red "${vhostdir} does not exist or is not a directory!"
            exit
        fi

        Install_Check_Acme.sh

        if [ -s ${Web_SSL_Dir}/${domain}/fullchain.cer ]; then
            echo "Removing exist domain certificate..."
            rm -rf ${Web_SSL_Dir}/${domain}
        fi

        if [ "${ssl_choice}" == "2" ]; then
            echo "Generate ssl certificate using Let's Encrypt..."
            /usr/local/acme.sh/acme.sh ${acme_sh_sudo} --server letsencrypt --issue ${letsdomain} -w ${vhostdir} -k 2048 --reloadcmd "${Web_Initd} reload"
        elif [ "${ssl_choice}" == "3" ]; then
            echo "Generate ssl certificate using BuyPass..."
            /usr/local/acme.sh/acme.sh ${acme_sh_sudo} --server buypass --issue ${letsdomain} -w ${vhostdir} -k 2048 --days 170 --reloadcmd "${Web_Initd} reload"
        elif [ "${ssl_choice}" == "4" ]; then
            echo "Generate ssl certificate using ZeroSSL..."
            /usr/local/acme.sh/acme.sh ${acme_sh_sudo} --server zerossl --issue ${letsdomain} -w ${vhostdir} -k 2048 --reloadcmd "${Web_Initd} reload"
        fi
        lets_status=$?

        ssl_certificate="${Web_SSL_Dir}/${domain}/fullchain.cer"
        ssl_certificate_key="${Web_SSL_Dir}/${domain}/${domain}.key"
        if [ "${lets_status}" = 0 ]; then
            Create_SSL_Config
            Echo_Green " Generate SSL Certificate successfully."
        else
            Echo_Red " Generate SSL Certificate failed!"
        fi
    fi
}

Add_Dns_SSL()
{
    provider=$1
    if [ "${provider}" != "" ]; then
        dns_provider="dns_${provider}"
    else
        Echo_Red "The dns manual mode can not renew automatically, you must renew it manually."
    fi

    Add_SSL_Info_Menu
    Add_DNS_SSL_Select_Menu
    Return_301_Menu
    Install_Check_Acme.sh

    if [[ ! -s /usr/local/acme.sh/dnsapi/dns_${provider}.sh && "${provider}" != "" ]]; then
        echo "DNS Provider: ${provider} not found."
        exit 1
    fi

    if [ -s ${Web_SSL_Dir}/${domain}/fullchain.cer ]; then
        echo "Removing exist domain certificate..."
        rm -rf ${Web_SSL_Dir}/${domain}
    fi

    letsdomain=""
    if [ "${moredomain}" != "" ]; then
        letsdomain="-d ${domain}"
        for i in ${moredomain};do
            letsdomain=${letsdomain}" -d ${i}"
        done
    else
        letsdomain="-d ${domain}"
    fi

    if echo "${letsdomain}" | grep -q '\*\.' && echo "${letsdomain}" | grep -qi 'www\.'; then
        Echo_Red "wildcard SSL certificate DO NOT allow add www. subdomain."
        exit 1
    fi

    if [ "${dns_ssl_choice}" == "1" ]; then
        ca_server="letsencrypt"
    elif [ "${dns_ssl_choice}" == "2" ]; then
        ca_server="zerossl"
    fi

    echo "Generate ssl certificate using ${ca_server}..."
    if [ "${provider}" != "" ]; then
        /usr/local/acme.sh/acme.sh ${acme_sh_sudo} --server ${ca_server} --issue ${letsdomain} -k 2048 --dns ${dns_provider} --reloadcmd "${Web_Initd} reload"
        lets_status=$?
    else
        /usr/local/acme.sh/acme.sh ${acme_sh_sudo} --server ${ca_server} --issue ${letsdomain} -k 2048 --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
        Echo_Yellow "Please add the above TXT record to the domain in 120 seconds!!!"
        echo
        Sleep_Sec 120
        /usr/local/acme.sh/acme.sh ${acme_sh_sudo} --renew ${letsdomain} --yes-I-know-dns-manual-mode-enough-go-ahead-please
        lets_status=$?
    fi
    if [ "${lets_status}" = 0 ] || [[ "${provider}" = "" && "${lets_status}" = 1 && -s "${Web_SSL_Dir}/${domain}/fullchain.cer" ]]; then
        if [ ! -d "${vhostdir}" ]; then
            echo "Create Virtual Host directory......"
            mkdir -p ${vhostdir}
            echo "set permissions of Virtual Host directory......"
            chmod -R 755 ${vhostdir}
            chown -R www:www ${vhostdir}
        fi

        if [ ! -s "${Vhost_Dir}/${domain}.conf" ]; then
            Add_VHost_Config
        fi
        ssl_certificate="${Web_SSL_Dir}/${domain}/fullchain.cer"
        ssl_certificate_key="${Web_SSL_Dir}/${domain}/${domain}.key"
        Create_SSL_Config
        Echo_Blue "------------------ SSL Certificate information as follows ------------------"
        Echo_Blue "| Domain: ${domain} ${moredomain}"
        Echo_Blue "| SSL Certificate: ${Web_SSL_Dir}/${domain}/fullchain.cer"
        Echo_Blue "| SSL Certificate Key: ${Web_SSL_Dir}/${domain}/${domain}.key"
        Echo_Blue "------------------------------------ ---------------------------------------"
        Echo_Green " Generate SSL Certificate successfully."
    else
        Echo_Red " Generate SSL Certificate failed!"
    fi
}

Add_Dns_SSL_Only()
{
    provider=$1
    if [ "${provider}" != "" ]; then
        dns_provider="dns_${provider}"
    else
        Echo_Red "The dns manual mode can not renew automatically, you must renew it manually."
    fi

    Add_DNS_SSL_Only_Info_Menu
    Install_Check_Acme.sh

    if [[ ! -s /usr/local/acme.sh/dnsapi/dns_${provider}.sh && "${provider}" != "" ]]; then
        echo "DNS Provider: ${provider} not found."
        exit 1
    fi

    if [ -s ${Web_SSL_Dir}/${domain}/fullchain.cer ]; then
        echo "Removing exist domain certificate..."
        rm -rf ${Web_SSL_Dir}/${domain}
    fi

    letsdomain=""
    if [ "${moredomain}" != "" ]; then
        letsdomain="-d ${domain}"
        for i in ${moredomain};do
            letsdomain=${letsdomain}" -d ${i}"
        done
    else
        letsdomain="-d ${domain}"
    fi

    if echo "${letsdomain}" | grep -q '\*\.' && echo "${letsdomain}" | grep -qi 'www\.'; then
        Echo_Red "wildcard SSL certificate DO NOT allow add www. subdomain."
        exit 1
    fi

    if [ "${dns_ssl_choice}" == "1" ]; then
        ca_server="letsencrypt"
    elif [ "${dns_ssl_choice}" == "2" ]; then
        ca_server="zerossl"
    fi

    echo "Starting create SSL Certificate use ${ca_server}..."
    if [ "${provider}" != "" ]; then
        /usr/local/acme.sh/acme.sh ${acme_sh_sudo} --server ${ca_server} --issue ${letsdomain} -k 2048 --dns ${dns_provider} --reloadcmd "${Web_Initd} reload"
        lets_status=$?
    else
        /usr/local/acme.sh/acme.sh ${acme_sh_sudo} --server ${ca_server} --issue ${letsdomain} -k 2048 --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
        Echo_Yellow "Please add the above TXT record to the domain in 120 seconds!!!"
        echo
        Sleep_Sec 120
        /usr/local/acme.sh/acme.sh ${acme_sh_sudo} --renew ${letsdomain} --yes-I-know-dns-manual-mode-enough-go-ahead-please
        lets_status=$?
    fi
    if [ "${lets_status}" = 0 ] || [[ "${provider}" = "" && "${lets_status}" = 1 && -s "${Web_SSL_Dir}/${domain}/fullchain.cer" ]]; then
        Echo_Blue "------------------ SSL Certificate information as follows ------------------"
        Echo_Blue "| Domain: ${domain} ${moredomain}"
        Echo_Blue "| SSL Certificate: ${Web_SSL_Dir}/${domain}/fullchain.cer"
        Echo_Blue "| SSL Certificate Key: ${Web_SSL_Dir}/${domain}/${domain}.key"
        Echo_Blue "------------------------------------ ---------------------------------------"
        Echo_Green " Generate SSL Certificate successfully."
    else
        Echo_Red " Generate SSL Certificate failed!"
    fi
}

Install_Check_Acme.sh()
{
    if [ -s /usr/local/acme.sh/acme.sh ]; then
        echo "/usr/local/acme.sh/acme.sh [found]"
        if ! cat /usr/local/acme.sh/account.conf | grep -Eq "^ACCOUNT_EMAIL="; then
            /usr/local/acme.sh/acme.sh --register-account -m ${email_address}
        fi
    else
        cd /tmp
        [[ -f latest.tar.gz ]] && rm -f latest.tar.gz
        wget https://soft.lnmp.com/lib/acme.sh/latest.tar.gz -O latest.tar.gz --prefer-family=IPv4 --no-check-certificate ||         wget https://github.com/acmesh-official/acme.sh/archive/refs/heads/master.tar.gz -O latest.tar.gz --no-check-certificate
        tar zxf latest.tar.gz
        cd acme.sh-*
        ./acme.sh --install ${acme_sh_sudo} --log --home /usr/local/acme.sh --certhome ${Web_SSL_Dir} -m ${email_address}
        cd ..
        rm -f latest.tar.gz
        rm -rf acme.sh-*
        sed -i 's/cat "\$CERT_PATH"$/#cat "\$CERT_PATH"/g' /usr/local/acme.sh/acme.sh
        cat >/usr/local/acme.sh/upgrade.sh<<EOF
#!/bin/bash

. "/usr/local/acme.sh/acme.sh.env"
/usr/local/acme.sh/acme.sh --upgrade
sed -i 's/cat "\\\$CERT_PATH"\$/#cat "\\\$CERT_PATH"/g' /usr/local/acme.sh/acme.sh
sed -i 's/DEFAULT_ACCOUNT_KEY_LENGTH=ec-256/DEFAULT_ACCOUNT_KEY_LENGTH=2048/g' /usr/local/acme.sh/acme.sh
sed -i 's/DEFAULT_DOMAIN_KEY_LENGTH=ec-256/DEFAULT_DOMAIN_KEY_LENGTH=2048/g' /usr/local/acme.sh/acme.sh
EOF

        chmod +x /usr/local/acme.sh/upgrade.sh
        #if crontab -l|grep -q "/usr/local/acme.sh/upgrade.sh"; then
        #    echo "acme.sh upgrade crontab rule is exist."
        #else
        #    echo "Add acme.sh upgrade crontab rule..."
        #    (crontab -l ; echo '0 3 */7 * * /usr/local/acme.sh/upgrade.sh') | crontab -
        #fi
        if command -v yum >/dev/null 2>&1; then
            yum -y update nss
            yum -y install ca-certificates
            service crond restart
            chkconfig crond on
        elif command -v apt-get >/dev/null 2>&1; then
            /etc/init.d/cron restart
            update-rc.d cron defaults
        fi
    fi

    . "/usr/local/acme.sh/acme.sh.env"
}

Add_SSL_Menu()
{
    if [ "${info}" == "n" ]; then
        Add_SSL_Info_Menu
    fi
    echo "1: Use your own SSL Certificate and Key"
    echo "2: Use Let's Encrypt to create SSL Certificate and Key"
    echo "3: Use BuyPass to create SSL Certificate and Key"
    echo "4: Use ZeroSSL to create SSL Certificate and Key"
    while :;do
        Echo_Yellow "Enter 1, 2, 3 or 4: "
        read ssl_choice || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
        if [ "${ssl_choice}" == "1" ]; then
            while :;do
                Echo_Yellow "Please enter full path to SSL Certificate file: "
                read ssl_certificate || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
                if [ "${ssl_certificate}" == "" ]; then
                    Echo_Red "SSL Certificate file cannot be empty!"
                else
                    break
                fi
            done
            while :;do
                Echo_Yellow "Please enter full path to SSL Certificate Key file: "
                read ssl_certificate_key || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
                if [ "${ssl_certificate_key}" == "" ]; then
                    Echo_Red "SSL Certificate Key file cannot be empty!"
                else
                    break
                fi
            done
            if [ "${Stack_Web}" = "apache" ]; then
                Echo_Yellow "Please enter full path to SSL Chain file（直接回车表示不设置）: "
                read ssl_chain
                if [ "${ssl_chain}" = "" ]; then
                    Echo_Yellow "SSL Chain file will not set."
                    Conf_SSLChain="#SSLCertificateChainFile /path/to/your/chain.pem"
                else
                    Conf_SSLChain="SSLCertificateChainFile ${ssl_chain}"
                fi
            fi
            break
        elif [[ "${ssl_choice}" =~ ^(2|3|4)$ ]]; then
            Check_Acme_EMail
            break
        else
            Echo_Red "Please Enter 1, 2, 3 or 4!"
        fi
    done

    Return_301_Menu
}

# ===== 多版本 PHP 探测与选择（单一事实源）=====
# 新增 PHP 版本只需改 MPHP_VERSIONS 一行；CLI（构建期内嵌）与 addons.sh
# （运行期 source include/php-select.sh）共用同一份实现，杜绝「改一处漏一处」。
# 选项编号沿用历史约定：2 起对应 MPHP_VERSIONS 的第 1 项，保证老用户手感不变。
MPHP_VERSIONS='5.2 5.3 5.4 5.5 5.6 7.0 7.1 7.2 7.3 7.4 8.0 8.1 8.2 8.3 8.4'
MPHP_ROOT="${MPHP_ROOT:-/usr/local}"
MPHP_NGXCONF="${MPHP_NGXCONF:-/usr/local/nginx/conf}"
MPHP_INITD="${MPHP_INITD:-/etc/init.d}"

# 探测已安装的多版本 PHP，结果写入 MPHP_FOUND；有则返回 0
PHP_Multi_Detect()
{
    MPHP_FOUND=''
    local v
    for v in ${MPHP_VERSIONS}; do
        if [[ -s "${MPHP_ROOT}/php${v}/sbin/php-fpm" && -s "${MPHP_NGXCONF}/enable-php${v}.conf" && -s "${MPHP_INITD}/php-fpm${v}" ]]; then
            MPHP_FOUND="${MPHP_FOUND}${v} "
        fi
    done
    MPHP_FOUND="${MPHP_FOUND% }"
    [ -n "${MPHP_FOUND}" ]
}

# 交互选择 PHP 版本；结果写入 PHP_Select_Ver（空串 = 主 PHP）
PHP_Multi_Select()
{
    PHP_Select_Ver=''
    PHP_Multi_Detect || return 0
    local cur idx v pick
    cur="$(${MPHP_ROOT}/php/bin/php-config --version 2>/dev/null)"
    echo "Multiple PHP version found, Please select the PHP version."
    Echo_Green "1: Default Main PHP ${cur}"
    idx=1
    for v in ${MPHP_VERSIONS}; do
        idx=$((idx + 1))
        case " ${MPHP_FOUND} " in
            *" ${v} "*) Echo_Green "${idx}: PHP ${v} [found]" ;;
        esac
    done
    Echo_Yellow "Enter your choice (1-${idx}): "
    read pick
    case "${pick}" in
        ''|1) return 0 ;;
    esac
    if [ "${pick}" -ge 2 ] 2>/dev/null && [ "${pick}" -le "${idx}" ] 2>/dev/null; then
        v="$(echo ${MPHP_VERSIONS} | cut -d' ' -f$((pick - 1)))"
        case " ${MPHP_FOUND} " in
            *" ${v} "*) PHP_Select_Ver="${v}"; echo "Current selection: PHP ${v}"; return 0 ;;
        esac
    fi
    echo "Default, Current selection: PHP ${cur}"
}

lnmp_start()
{
    echo "Starting LNMP..."
    /etc/init.d/nginx start
    /etc/init.d/mysql start
    /etc/init.d/php-fpm start
    for mphpfpm in /etc/init.d/php-fpm[578].[0-9]
    do
        if [ -f ${mphpfpm} ]; then
            ${mphpfpm} start
        fi
    done
}

lnmp_stop()
{
    echo "Stoping LNMP..."
    /etc/init.d/nginx stop
    /etc/init.d/mysql stop
    /etc/init.d/php-fpm stop
    for mphpfpm in /etc/init.d/php-fpm[578].[0-9]
    do
        if [ -f ${mphpfpm} ]; then
            ${mphpfpm} stop
        fi
    done
}

lnmp_reload()
{
    echo "Reload LNMP..."
    /etc/init.d/nginx reload
    /etc/init.d/mysql reload
    /etc/init.d/php-fpm reload
    for mphpfpm in /etc/init.d/php-fpm[578].[0-9]
    do
        if [ -f ${mphpfpm} ]; then
            ${mphpfpm} reload
        fi
    done
}

lnmp_kill()
{
    echo "Kill nginx,php-fpm,mysql process..."
    pkill -9 nginx 2>/dev/null
    pkill -9 mysqld 2>/dev/null
    pkill -9 php-fpm 2>/dev/null
    pkill -9 php-cgi 2>/dev/null
    echo "done."
}

lnmp_status()
{
    /etc/init.d/nginx status
    if [ -f $PHPFPMPIDFILE ]; then
        echo "php-fpm is running!"
    else
        echo "php-fpm is stop!"
    fi
    /etc/init.d/mysql status
}

Add_VHost_Config()
{
    if [ ! -f /usr/local/nginx/conf/rewrite/${rewrite}.conf ]; then
        echo "Create Virtual Host Rewrite file......"
        touch /usr/local/nginx/conf/rewrite/${rewrite}.conf
        echo "Create rewrite file successful,You can add rewrite rule into /usr/local/nginx/conf/rewrite/${rewrite}.conf."
    else
        echo "You select the exist rewrite rule:/usr/local/nginx/conf/rewrite/${rewrite}.conf"
    fi

    cat >"/usr/local/nginx/conf/vhost/${domain}.conf"<<EOF
server
    {
        listen 80;
        #listen [::]:80;
        server_name ${domain} ${moredomain};
        index index.html index.htm index.php default.html default.htm default.php;
        root  ${vhostdir};

        include rewrite/${rewrite}.conf;
        #error_page   404   /404.html;

        # Deny access to PHP files in specific directory
        #location ~ /(wp-content|uploads|wp-includes|images)/.*\.php$ { deny all; }

        ${include_enable_php}

        location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
        {
            expires      30d;
        }

        location ~ .*\.(js|css)?$
        {
            expires      12h;
        }

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

        location ~ /\.
        {
            deny all;
        }

        ${al}
    }
EOF

    if [ "${enable_ipv6}" == "y" ]; then
        sed -i 's/#listen \[::\]:80;/listen \[::\]:80;/g' /usr/local/nginx/conf/vhost/${domain}.conf
    fi

    echo "Test Nginx configure file......"
    /usr/local/nginx/sbin/nginx -t
    echo "Reload Nginx......"
    /usr/local/nginx/sbin/nginx -s reload
}

Multiple_PHP_Select()
{
    #@ 探测与选择逻辑共用 conf/cli/php-select.sh（addons.sh 走 include/php-select.sh 同源）
    PHP_Multi_Select
    local sfx="${PHP_Select_Ver}"
    if [ "${enable_pathinfo}" == "y" ]; then
        include_enable_php="include enable-php${sfx}-pathinfo.conf;"
        if [ -n "${sfx}" ] && [ ! -s "${MPHP_NGXCONF}/enable-php${sfx}-pathinfo.conf" ]; then
            \cp ${MPHP_NGXCONF}/enable-php-pathinfo.conf ${MPHP_NGXCONF}/enable-php${sfx}-pathinfo.conf
            sed -i "s/php-cgi.sock/php-cgi${sfx}.sock/g" ${MPHP_NGXCONF}/enable-php${sfx}-pathinfo.conf
        fi
    else
        include_enable_php="include enable-php${sfx}.conf;"
    fi
}

Add_VHost()
{
    domain=""
    while :;do
        echo "请输入主域名（例如：nextlnmp.cn）："
        read -e domain || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
        if [ "${domain}" != "" ] && [[ "$domain" = "${domain%[[:space:]]*}" ]]; then
            if [ -f "/usr/local/nginx/conf/vhost/${domain}.conf" ]; then
                Echo_Red " ${domain} 已存在，请检查！"
                exit 1
            else
                echo " 主域名：${domain}"
            fi
            break
        else
            Echo_Red "域名不能为空或包含空格，请重新输入！"
        fi
    done

    echo ""
    echo "请输入其他绑定域名，多个用空格分隔（例如：www.nextlnmp.cn，直接回车跳过）："
    read -e moredomain
    if [ "${moredomain}" != "" ]; then
        echo " 绑定域名列表：${domain} ${moredomain}"
    fi

    vhostdir="/home/wwwroot/${domain}"
    echo ""
    echo "请输入网站根目录（直接回车使用默认）："
    echo "默认目录：${vhostdir}"
    # 这个提示【回车就是采用默认值】，所以读到 EOF 等价于回车，不能像必填项
    # 那样直接 exit ——否则用管道/重定向喂答案的自动化建站会死在这里。
    # （EOF 护栏只该加在"空值无法继续"的必填项上。）
    read -e input_vhostdir || input_vhostdir=""
    while [ "${input_vhostdir}" != "" ] && ! Check_VHost_Dir "${input_vhostdir}"; do
        echo "请重新输入网站根目录（直接回车使用默认 /home/wwwroot/${domain}）："
        read -e input_vhostdir || { input_vhostdir=""; break; }
    done
    if [ "${input_vhostdir}" != "" ]; then
        vhostdir="${input_vhostdir}"
    fi
    echo "网站目录：${vhostdir}"

    echo ""
    echo "请选择伪静态规则（几乎所有建站程序都需要）："
    echo "1. WordPress（推荐，博客/企业站）"
    echo "2. Typecho（轻量博客）"
    echo "3. Discuz X（论坛）"
    echo "4. ThinkPHP"
    echo "5. Laravel"
    echo "6. CodeIgniter"
    echo "7. Yii2"
    echo "8. ZBlog"
    echo "9. 不需要伪静态"
    echo "请输入数字（直接回车默认选 1 WordPress）："
    read -e rewrite_select
    case "${rewrite_select}" in
        2) rewrite="typecho" ;;
        3) rewrite="discuzx" ;;
        4) rewrite="thinkphp" ;;
        5) rewrite="laravel" ;;
        6) rewrite="codeigniter" ;;
        7) rewrite="yii2" ;;
        8) rewrite="zblog" ;;
        9) rewrite="none" ;;
        *) rewrite="wordpress" ;;
    esac
    echo "已选择伪静态规则：${rewrite}"

    # Typecho/ThinkPHP/Laravel/Yii2 需要 pathinfo，自动开启
    if [[ "${rewrite}" == "typecho" || "${rewrite}" == "thinkphp" || "${rewrite}" == "laravel" || "${rewrite}" == "yii2" ]]; then
        enable_pathinfo="y"
        Echo_Green "已自动启用 Pathinfo（${rewrite} 需要此功能）。"
    else
        echo ""
        echo "是否启用 PHP Pathinfo？（Typecho/ThinkPHP/Laravel 需要，WordPress/Discuz 不需要）[y/N] "
        read -e enable_pathinfo
        if [[ "${enable_pathinfo}" == "y" || "${enable_pathinfo}" == "Y" ]]; then
            echo "已启用 Pathinfo。"
            enable_pathinfo="y"
        else
            echo "Pathinfo 已关闭。"
            enable_pathinfo="n"
        fi
    fi

    echo ""
    echo "是否记录访问日志？（会占用磁盘空间，直接回车关闭）[y/N] "
    read -e access_log
    if [[ "${access_log}" == "y" || "${access_log}" == "Y" ]]; then
        al_name="${domain}"
        al="access_log  /home/wwwlogs/${al_name}.log;"
        echo "访问日志：/home/wwwlogs/${al_name}.log"
        access_log="y"
    else
        echo "访问日志已关闭。"
        al="access_log off;"
        access_log="n"
    fi

    echo ""
    echo "是否启用 IPv6？（不了解请直接回车关闭）[y/N] "
    read -e enable_ipv6
    if [[ "${enable_ipv6}" == "y" || "${enable_ipv6}" == "Y" ]]; then
        echo "已启用 IPv6。"
        enable_ipv6="y"
    else
        echo "IPv6 已关闭。"
        enable_ipv6="n"
    fi

    Multiple_PHP_Select

    if [[ -s /usr/local/mysql/bin/mysql || -s /usr/local/mariadb/bin/mysql ]]; then
        echo ""
        echo "是否自动创建数据库和数据库用户？（推荐创建，直接回车默认创建）[Y/n] "
        read -e create_database
        if [[ "${create_database}" == "n" || "${create_database}" == "N" ]]; then
            create_database="n"
        else
            create_database="y"
            Verify_DB_Password
            Add_Database_Menu
        fi
    fi

    if [ -s /usr/local/pureftpd/sbin/pure-ftpd ]; then
        Echo_Yellow "是否创建 FTP 账号？[y/N] "
        read create_ftp

        if [ "${create_ftp}" == "y" ]; then
            Add_Ftp_Menu
        fi
    fi

    echo ""
    echo "是否申请 SSL 证书？（域名已解析完成选 y，未解析选 n，之后可用 nextlnmp ssl add 补申请）[y/N] "
    read -e create_ssl
    if [[ "${create_ssl}" == "y" || "${create_ssl}" == "Y" ]]; then
        create_ssl="y"
        Add_SSL_Menu
    else
        create_ssl="n"
    fi

    echo ""
    echo "按任意键开始创建虚拟主机..."
    OLDCONFIG=`stty -g`
    stty -icanon -echo min 1 time 0
    dd count=1 2>/dev/null
    stty ${OLDCONFIG}

    echo "创建网站目录..."
    mkdir -p "${vhostdir}"
    if [ "${access_log}" == "y" ]; then
        touch /home/wwwlogs/${al_name}.log
    fi
    # 解除可能残留的 chattr 不可变属性
    if [ -f "${vhostdir}/.user.ini" ]; then
        chattr -i "${vhostdir}/.user.ini" 2>/dev/null
        rm -f "${vhostdir}/.user.ini"
    fi
    echo "设置目录权限..."
    chmod -R 755 "${vhostdir}"
    chown -R www:www "${vhostdir}"
    Add_VHost_Config

    cat >"${vhostdir}/.user.ini"<<EOF
open_basedir=${vhostdir}:/tmp/:/proc/
EOF
    chmod 644 "${vhostdir}/.user.ini"
    chattr +i "${vhostdir}/.user.ini"

    # 兼容 Binary 安装的多版本 php-fpm
    for phpfpm in /etc/init.d/php-fpm /etc/init.d/php-fpm[578].[0-9]; do
        [ -f "$phpfpm" ] && $phpfpm reload
    done

    if [ "${create_database}" == "y" ]; then
        Add_Database
    fi

    if [ "${create_ftp}" == "y" ]; then
        Add_Ftp
    fi

    if [ "${create_ssl}" == "y" ]; then
        Add_SSL
    fi

    Echo_Green "================================================"
    echo "虚拟主机信息："
    echo "域名：${domain}"
    echo "网站目录：${vhostdir}"
    echo "伪静态：${rewrite}"
    if [ "${access_log}" == "n" ]; then
        echo "访问日志：关闭"
    else
        echo "访问日志：/home/wwwlogs/${al_name}.log"
    fi
    # 按【真实结果】打印，不按用户当初的 y/n。原来失败也照样吐出一套
    # 不存在的凭据，用户拿去装 WordPress 才发现连不上，完全想不到问题出在建站阶段。
    if [ "${create_database}" == "y" ]; then
        if [ "${Add_DB_Result}" = "ok" ]; then
            echo "数据库名：${database_name}"
            echo "数据库用户：${database_name}"
            echo "数据库密码：${mysql_password}"
        else
            Echo_Red "数据库：创建失败（上面有具体报错），以下凭据【不可用】"
            Echo_Red "  想重试：nextlnmp database add"
        fi
    else
        echo "创建数据库：否"
    fi
    if [ "${create_ftp}" == "y" ]; then
        if [ "${Add_FTP_Result}" = "ok" ]; then
            echo "FTP 用户：${ftp_account_name}"
            echo "FTP 密码：${ftp_account_password}"
        else
            Echo_Red "FTP：创建失败（账号可能已存在），以下凭据【不可用】"
        fi
    fi
    if [ "${create_ssl}" == "y" ]; then
        echo "SSL 证书：已申请"
        if [ "${ssl_choice}" == "2" ]; then
            echo "  => Let's Encrypt"
        elif [ "${ssl_choice}" == "3" ]; then
            echo "  => BuyPass"
        elif [ "${ssl_choice}" == "4" ]; then
            echo "  => ZeroSSL"
        elif [ "${ssl_choice}" == "1" ]; then
            echo "  => 自有证书"
        fi
    fi
    if [ "${enable_ipv6}" == "y" ]; then
        echo "IPv6：已启用"
    fi
    if [ "${enable_pathinfo}" == "y" ]; then
        echo "Pathinfo：已启用"
    fi
    Echo_Green "================================================"
    echo ""
    echo "💡 部署网站程序提示："
    echo "   cd ${vhostdir}"
    echo "   chattr -i .user.ini        # 先解锁"
    echo "   （上传/解压网站文件...）"
    echo "   chown -R www:www .         # 设置权限"
    echo "   chattr +i .user.ini        # 再锁回"
}

Del_VHost()
{
    echo "======================================="
    echo "当前虚拟主机："
    List_VHost
    echo "======================================="
    domain=""
    while :;do
        Echo_Yellow "请输入要删除的域名："
        read domain || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
        if [ "${domain}" == "" ]; then
            Echo_Red "域名不能为空！"
        else
            break
        fi
    done
    if [ ! -f "/usr/local/nginx/conf/vhost/${domain}.conf" ]; then
        Echo_Red "${domain} 不存在！"
        exit 1
    else
        vhostdir=$(grep -m1 'root ' /usr/local/nginx/conf/vhost/${domain}.conf | awk '{print $2}' | sed 's/;//g')
        if [ -f "${vhostdir}/.user.ini" ]; then
            chattr -i "${vhostdir}/.user.ini"
            rm -f "${vhostdir}/.user.ini"
        fi
        rm -f /usr/local/nginx/conf/vhost/${domain}.conf
        echo "Reload Nginx..."
        /usr/local/nginx/sbin/nginx -s reload
        echo "========================================================"
        echo "✓ 虚拟主机 ${domain} 已删除"
        echo "  网站文件未删除（安全起见），如需删除请手动执行："
        echo "  rm -rf ${vhostdir}"
        echo "========================================================"
    fi
}

Add_SSL_Info_Menu()
{
    domain=""
    while :;do
        Echo_Yellow "Please enter domain(example: www.nextlnmp.com): "
        read domain || { Echo_Red "读到输入结束（EOF）：本命令需要交互输入，请在终端下运行。"; exit 1; }
        if [ "${domain}" != "" ] && [[ "$domain" = "${domain%[[:space:]]*}" ]]; then
            echo " Your domain: ${domain}"
            break
        else
            Echo_Red "Domain name can't be empty or contain spaces!"
        fi
    done

    Echo_Yellow "Enter more domain name(example: nextlnmp.com sub.nextlnmp.com): "
    read moredomain
    if [ "${moredomain}" != "" ]; then
        echo " domain list: ${moredomain}"
    fi

    Echo_Yellow "Please enter the directory for the domain: $domain"
    echo
    echo "Default directory: /home/wwwroot/${domain}: "
    read vhostdir
    if [ "${vhostdir}" == "" ]; then
        vhostdir="/home/wwwroot/${domain}"
    fi
    echo "Virtual Host Directory: ${vhostdir}"

    Echo_Yellow "Allow Rewrite rule? (y/n) "
    read allow_rewrite
    if [[ "${allow_rewrite}" == "n" || "${allow_rewrite}" == "" ]]; then
        rewrite="none"
    elif [ "${allow_rewrite}" == "y" ]; then
        rewrite="other"
        echo "Please enter the rewrite of programme, "
        echo "wordpress,discuzx,typecho,thinkphp,laravel,codeigniter,yii2,zblog rewrite was exist."
        Echo_Yellow "(Default rewrite: other): "
        read rewrite
        if [ "${rewrite}" == "" ]; then
            rewrite="other"
        fi
    fi
    echo "You choose rewrite: ${rewrite}"

    Echo_Yellow "Allow access log? (y/n) "
    read access_log
    if [[ "${access_log}" == "n" || "${access_log}" == "" ]]; then
        echo "Disable access log."
        al="access_log off;"
    else
        Echo_Yellow "Enter access log filename(Default:${domain}.log): "
        read al_name
        if [ "${al_name}" == "" ]; then
            al_name="${domain}"
        fi
        al="access_log  /home/wwwlogs/${al_name}.log;"
        echo "You access log filename: ${al_name}.log"
    fi

    Echo_Yellow "Enable PHP Pathinfo? (y/n) "
    read enable_pathinfo
    if [[ "${enable_pathinfo}" == "n" || "${enable_pathinfo}" == "" ]]; then
        echo "Disable pathinfo."
    elif [ "${allow_rewrite}" == "y" ]; then
        echo "Enable pathinfo."
        enable_pathinfo="y"
    fi

    Echo_Yellow "Enable IPv6? (y/n) "
    read enable_ipv6
    if [[ "${enable_ipv6}" == "n" || "${enable_ipv6}" == "" ]]; then
        echo "Disabled IPv6 Support in current Virtualhost."
        enable_ipv6="n"
    else
        echo "Enabled IPv6 Support in current Virtualhost."
        enable_ipv6="y"
    fi

    Multiple_PHP_Select
}

Create_SSL_Config()
{
    if [ ! -s /usr/local/nginx/conf/ssl/dhparam.pem ]; then
        echo "Create dhparam.pem..."
        mkdir -p /usr/local/nginx/conf/ssl/
        openssl dhparam -out /usr/local/nginx/conf/ssl/dhparam.pem 2048
    fi

    cat >>"/usr/local/nginx/conf/vhost/${domain}.conf"<<EOF

server
    {
        listen 443 ssl;
        http2 on;
        #listen [::]:443 ssl;
        ##listen [::]:443 ssl;
        server_name ${domain} ${moredomain};
        index index.html index.htm index.php default.html default.htm default.php;
        root  ${vhostdir};

        ssl_certificate ${ssl_certificate};
        ssl_certificate_key ${ssl_certificate_key};
        ssl_session_timeout 5m;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers on;
        ssl_ciphers "TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5";
        ssl_session_cache builtin:1000 shared:SSL:10m;
        # openssl dhparam -out /usr/local/nginx/conf/ssl/dhparam.pem 2048
        ssl_dhparam /usr/local/nginx/conf/ssl/dhparam.pem;

        include rewrite/${rewrite}.conf;
        #error_page   404   /404.html;

        # Deny access to PHP files in specific directory
        #location ~ /(wp-content|uploads|wp-includes|images)/.*\.php$ { deny all; }

        ${include_enable_php}

        location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
        {
            expires      30d;
        }

        location ~ .*\.(js|css)?$
        {
            expires      12h;
        }

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

        location ~ /\.
        {
            deny all;
        }

        ${al}
    }
EOF

    if [ "${using_301}" == "y" ]; then
        # 这里有两个坑，都是真机审计翻出来的：
        #  ① 原来插的是 location / { return 301 ... }。可 :80 段里有
        #     include enable-php.conf，那是【正则】location（location ~ \.php），
        #     而 nginx 的正则 location 优先于前缀 location —— 于是所有 .php 请求
        #     压根轮不到这个 301，PHP 站点的绝大多数页面从不跳 HTTPS。
        #     改用 ^~ 前缀：命中后 nginx 直接跳过正则匹配，对 .php 也生效。
        #  ② 原来无条件插入。对同一域名第二次配 SSL 就会插出第二个 location /，
        #     nginx 报 duplicate location 直接是致命错误，reload 静默失败，
        #     等到下次重启才发现全站起不来。所以先查再插。
        # acme-challenge 用更长的 ^~ 前缀单独放行，保证证书续期仍能走明文 HTTP。
        if ! grep -q "return 301 https://\$host\$request_uri;" /usr/local/nginx/conf/vhost/${domain}.conf; then
            sed -i '0,/access_log/!b;//i\        location ^~ /.well-known/acme-challenge/ {\n            allow all;\n        }\n        location ^~ / {\n            return 301 https://$host$request_uri;\n        }\n' /usr/local/nginx/conf/vhost/${domain}.conf
        fi
        sed -i "0,/include rewrite\/${rewrite}.conf;/s//#include rewrite\/${rewrite}.conf;/" /usr/local/nginx/conf/vhost/${domain}.conf
    fi

    if [ "${enable_ipv6}" == "y" ]; then
        sed -i 's/#listen \[::\]:443 ssl;/listen \[::\]:443 ssl;/g' /usr/local/nginx/conf/vhost/${domain}.conf
    fi

    echo "Test Nginx configure file......"
    /usr/local/nginx/sbin/nginx -t
    echo "Reload Nginx......"
    /usr/local/nginx/sbin/nginx -s reload
}


Check_DB

case "${arg1}" in
    start)
        lnmp_start
        ;;
    stop)
        lnmp_stop
        ;;
    restart)
        lnmp_stop
        lnmp_start
        ;;
    reload)
        lnmp_reload
        ;;
    kill)
        lnmp_kill
        ;;
    status)
        lnmp_status
        ;;
    nginx)
        /etc/init.d/nginx ${arg2}
        ;;
    mysql)
        /etc/init.d/mysql ${arg2}
        ;;
    mariadb)
        /etc/init.d/mariadb ${arg2}
        ;;
    php-fpm)
        /etc/init.d/php-fpm ${arg2}
        ;;
    pureftpd)
        /etc/init.d/pureftpd ${arg2}
        ;;
    vhost)
        Function_Vhost ${arg2}
        ;;
    database)
        Verify_DB_Password
        Function_Database ${arg2}
        TempMycnf_Clean
        ;;
    ftp)
        Check_Pureftpd
        Function_Ftp ${arg2}
        ;;
    ssl)
        case "${arg2}" in
            add)
                echo ""
                echo "当前已有站点列表："
                echo "======================================="
                vhost_list=($(ls /usr/local/nginx/conf/vhost/ | grep ".conf$" | sed 's/.conf//g'))
                for i in "${!vhost_list[@]}"; do
                    echo "$((i+1)). ${vhost_list[$i]}"
                done
                echo "======================================="
                Echo_Yellow "请输入数字选择要申请证书的站点（直接回车选第 1 个）："
                read -e ssl_vhost_select
                if [ "${ssl_vhost_select}" == "" ]; then
                    ssl_vhost_select=1
                fi
                domain="${vhost_list[$((ssl_vhost_select-1))]}"
                if [ "${domain}" == "" ]; then
                    Echo_Red "选择无效！"
                    exit 1
                fi
                echo "已选择站点：${domain}"
                moredomain=$(grep "server_name" /usr/local/nginx/conf/vhost/${domain}.conf | head -1 | sed "s/server_name//g" | sed "s/${domain}//g" | sed "s/;//g" | tr -s ' ')
                vhostdir=$(grep "root " /usr/local/nginx/conf/vhost/${domain}.conf | head -1 | awk '{print $2}' | sed 's/;//g')
                if [ "${vhostdir}" == "" ]; then
                    vhostdir="/home/wwwroot/${domain}"
                fi
                echo "绑定域名：${domain} ${moredomain}"
                echo "网站目录：${vhostdir}"
                # 从现有 80 端口配置继承 rewrite/PHP/日志设置，缺省兜底，避免生成 include rewrite/.conf; 这类损坏配置
                rewrite=$(grep -oE 'include rewrite/[^.]+\.conf' /usr/local/nginx/conf/vhost/${domain}.conf | head -1 | sed 's#include rewrite/##;s#\.conf##')
                [ -z "${rewrite}" ] && rewrite="none"
                include_enable_php=$(grep -oE 'include enable-php[^;]*\.conf;' /usr/local/nginx/conf/vhost/${domain}.conf | head -1)
                [ -z "${include_enable_php}" ] && include_enable_php="include enable-php.conf;"
                al=$(grep -E '^\s*access_log' /usr/local/nginx/conf/vhost/${domain}.conf | head -1 | sed 's/^ *//')
                [ -z "${al}" ] && al="access_log off;"
                info="y"
                Add_SSL_Menu
                Add_SSL
                ;;
            *)
                info="n"
                Add_SSL_Menu
                Add_SSL
                ;;
        esac
        ;;
    dnsssl|dns)
        Add_Dns_SSL ${arg2}
        ;;
    onlyssl)
        Add_Dns_SSL_Only ${arg2}
        ;;
    info)
        if [ -f /root/nextlnmp-info.txt ]; then
            cat /root/nextlnmp-info.txt
        else
            echo "安装信息文件不存在，请运行 nextlnmp 安装后查看"
        fi
        ;;
    password)
        if [ "$2" = "--delete" ]; then
            if [ -f /root/.nextlnmp_db_password ]; then
                rm -f /root/.nextlnmp_db_password
                echo "✓ 密码文件已删除"
            else
                echo "密码文件不存在"
            fi
        else
            if [ -f /root/.nextlnmp_db_password ]; then
                echo "数据库 root 密码：$(cat /root/.nextlnmp_db_password)"
                echo ""
                echo "记住后执行 nextlnmp password --delete 删除密码文件"
            else
                echo "密码文件不存在（已删除或未生成）"
                echo "如需重置密码：bash tools/reset_mysql_root_password.sh"
            fi
        fi
        ;;
    *)
        echo "Usage: nextlnmp {start|stop|reload|restart|kill|status}"
        echo "Usage: nextlnmp {nginx|mysql|mariadb|php-fpm|pureftpd} {start|stop|reload|restart|kill|status}"
        echo "Usage: nextlnmp vhost {add|list|del}"
        echo "Usage: nextlnmp database {add|list|edit|del}"
        echo "Usage: nextlnmp ftp {add|list|edit|del|show}"
        echo "Usage: nextlnmp ssl add"
        echo "Usage: nextlnmp {dnsssl|dns} {cx|ali|cf|dp|he|gd|aws}"
        echo "Usage: nextlnmp onlyssl {cx|ali|cf|dp|he|gd|aws}"
        ;;
esac
exit
