feat: add unified installer management menu

This commit is contained in:
zxyszx
2026-08-03 23:29:02 +08:00
parent b55c298ef4
commit 697ed236cc
3 changed files with 84 additions and 43 deletions
+4 -4
View File
@@ -35,10 +35,10 @@ curl -fsSL https://raw.githubusercontent.com/zxyszx/NewSzxcn-Email/main/install.
bash <(curl -fsSL https://raw.githubusercontent.com/zxyszx/NewSzxcn-Email/main/install.sh) bash <(curl -fsSL https://raw.githubusercontent.com/zxyszx/NewSzxcn-Email/main/install.sh)
``` ```
空白服务器会进入防火墙、邮件域名、管理员账号和 Web 部署方式的引导。检测到 脚本会先显示统一管理菜单。空白服务器默认选择安装,并进入防火墙、邮件域名、管理员
`/opt/newszxcn-email/.env` 时,脚本会先询问是更新现有邮局、修复现有安装、退出, 账号和 Web 部署方式的引导;检测到已有安装时默认选择安全更新。选择重新安装会先将
还是完整备份旧目录后全新安装;不会再直接跳过引导并重启服务。更新会先备份数据库, `/opt/newszxcn-email` 完整改名备份,再进入首次安装流程。更新会先备份数据库,并在
并在启动失败时自动回滚。 启动失败时自动回滚。
脚本会自动完成: 脚本会自动完成:
+71 -22
View File
@@ -4,7 +4,7 @@ set -Eeuo pipefail
REPOSITORY="zxyszx/NewSzxcn-Email" REPOSITORY="zxyszx/NewSzxcn-Email"
RAW_BASE="https://raw.githubusercontent.com/${REPOSITORY}/main" RAW_BASE="https://raw.githubusercontent.com/${REPOSITORY}/main"
INSTALL_DIR="${LANQIN_INSTALL_DIR:-/opt/newszxcn-email}" INSTALL_DIR="${LANQIN_INSTALL_DIR:-/opt/newszxcn-email}"
COMMAND="${1:-install}" COMMAND="${1:-menu}"
ROLLBACK_FILE="${INSTALL_DIR}/.rollback-image" ROLLBACK_FILE="${INSTALL_DIR}/.rollback-image"
NGINX_CONFIG="/etc/nginx/conf.d/newszxcn-email.conf" NGINX_CONFIG="/etc/nginx/conf.d/newszxcn-email.conf"
ACME_WEBROOT="/var/www/newszxcn-acme" ACME_WEBROOT="/var/www/newszxcn-acme"
@@ -21,7 +21,8 @@ NewSzxcn Email 管理命令
用法:newszxcn-email <command> 用法:newszxcn-email <command>
install 首次安装;检测到已有安装时显示操作菜单 menu 显示安装与运维菜单
install 首次安装;已有安装会先完整备份再重新安装
update 备份数据库并更新到最新版 update 备份数据库并更新到最新版
status 查看容器与健康状态 status 查看容器与健康状态
logs 持续查看运行日志 logs 持续查看运行日志
@@ -163,19 +164,24 @@ prompt_choice() {
done done
} }
prompt_existing_install_action() { prompt_menu_choice() {
local public_url action local default_value="$1" value="${LANQIN_MENU_ACTION:-}"
public_url="$(env_value LANQIN_PUBLIC_BASE_URL || true)" if [[ -z "${value}" ]] && ! has_tty; then
prompt_text "\n[发现] 检测到已有 NewSzxcn Email 安装:${INSTALL_DIR}\n" fail "非交互环境请直接使用 install、update、status 等子命令。"
if [[ -n "${public_url}" ]]; then
prompt_text "[发现] 当前访问地址:${public_url}\n"
fi fi
prompt_text '请选择操作 [1]:\n1. 更新现有邮局(推荐,自动备份并支持回滚)\n2. 修复现有安装(保留配置和数据)\n3. 退出,不做任何修改\n4. 备份现有安装并全新安装\n' while true; do
if [[ -z "${LANQIN_EXISTING_ACTION:-}" ]] && ! has_tty; then if [[ -z "${value}" ]] && has_tty; then
fail "非交互环境不能选择已有安装操作;更新请执行 newszxcn-email update。" read -r -p "请选择 [${default_value}]: " value </dev/tty
fi fi
action="$(prompt_choice LANQIN_EXISTING_ACTION "请选择 [1]: " "1" "4")" value="${value:-${default_value}}"
printf '%s' "${action}" if [[ "${value}" =~ ^[0-9]$ ]]; then
printf '%s' "${value}"
return
fi
prompt_text "[提示] 请输入 0 至 9。\n"
value=""
has_tty || fail "LANQIN_MENU_ACTION 必须设置为 0 至 9。"
done
} }
has_tty() { has_tty() {
@@ -605,15 +611,8 @@ do_repair_install() {
} }
do_install() { do_install() {
local action
if [[ -f "${INSTALL_DIR}/.env" ]]; then if [[ -f "${INSTALL_DIR}/.env" ]]; then
action="$(prompt_existing_install_action)" do_backup_reinstall
case "${action}" in
1) do_update ;;
2) do_repair_install ;;
3) success "已退出,现有邮局未作修改。" ;;
4) do_backup_reinstall ;;
esac
return return
fi fi
@@ -749,6 +748,55 @@ do_backup_reinstall() {
do_install do_install
} }
do_menu() {
local installed="false" default_choice="1" public_url="" choice
if [[ -f "${INSTALL_DIR}/.env" ]]; then
installed="true"
default_choice="2"
public_url="$(env_value LANQIN_PUBLIC_BASE_URL || true)"
fi
prompt_text '\n==================================================\n'
prompt_text ' NewSzxcn Email 一键安装与管理\n'
prompt_text '==================================================\n'
if [[ "${installed}" == "true" ]]; then
prompt_text " 状态:已安装\n 路径:${INSTALL_DIR}\n"
[[ -n "${public_url}" ]] && prompt_text " 地址:${public_url}\n"
else
prompt_text ' 状态:未安装\n'
fi
prompt_text '--------------------------------------------------\n'
prompt_text ' 1. 安装 / 重新安装(旧数据自动备份)\n'
prompt_text ' 2. 更新系统(数据库自动备份)\n'
prompt_text ' 3. 修复现有安装\n'
prompt_text ' 4. 查看运行状态\n'
prompt_text ' 5. 重启服务\n'
prompt_text ' 6. 查看实时日志\n'
prompt_text ' 7. 申请或续期 SSL 证书\n'
prompt_text ' 8. 回滚上个命令行版本\n'
prompt_text ' 9. 卸载服务(保留数据)\n'
prompt_text ' 0. 退出\n'
prompt_text '==================================================\n'
choice="$(prompt_menu_choice "${default_choice}")"
if [[ "${choice}" != "0" && "${choice}" != "1" && "${installed}" != "true" ]]; then
fail "尚未安装,请先选择 1。"
fi
case "${choice}" in
0) success "已退出,未作任何修改。" ;;
1) do_install ;;
2) do_update ;;
3) do_repair_install ;;
4) ensure_docker; do_status ;;
5) do_restart ;;
6) ensure_docker; compose logs -f --tail=200 lanqin-email updater ;;
7) do_certificate ;;
8) ensure_docker; do_rollback ;;
9) ensure_docker; do_uninstall ;;
esac
}
if [[ "${LANQIN_SOURCE_ONLY:-false}" == "true" ]]; then if [[ "${LANQIN_SOURCE_ONLY:-false}" == "true" ]]; then
if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then
return 0 return 0
@@ -758,6 +806,7 @@ fi
case "${COMMAND}" in case "${COMMAND}" in
help|-h|--help) usage ;; help|-h|--help) usage ;;
menu) require_root; require_curl; do_menu ;;
install) require_root; require_curl; do_install ;; install) require_root; require_curl; do_install ;;
update) require_root; require_curl; do_update ;; update) require_root; require_curl; do_update ;;
status) require_root; require_curl; ensure_docker; do_status ;; status) require_root; require_curl; ensure_docker; do_status ;;
+9 -17
View File
@@ -109,22 +109,14 @@ test_legacy_configuration_is_preserved() {
assert_eq "127.0.0.1:9090" "$(env_value LANQIN_HTTP_BIND)" "legacy HTTP bind" assert_eq "127.0.0.1:9090" "$(env_value LANQIN_HTTP_BIND)" "legacy HTTP bind"
} }
test_existing_install_action() { test_menu_choice() {
local temp_dir export LANQIN_MENU_ACTION=0
temp_dir="$(mktemp -d)" assert_eq "0" "$(prompt_menu_choice 1)" "menu exit action"
cp "${ROOT_DIR}/deploy/.env.example" "${temp_dir}/.env" export LANQIN_MENU_ACTION=1
export INSTALL_DIR="${temp_dir}" assert_eq "1" "$(prompt_menu_choice 2)" "menu install action"
set_env LANQIN_PUBLIC_BASE_URL "https://mail.example.com" export LANQIN_MENU_ACTION=9
assert_eq "9" "$(prompt_menu_choice 1)" "menu uninstall action"
export LANQIN_EXISTING_ACTION=1 unset LANQIN_MENU_ACTION
assert_eq "1" "$(prompt_existing_install_action)" "existing install update action"
export LANQIN_EXISTING_ACTION=2
assert_eq "2" "$(prompt_existing_install_action)" "existing install repair action"
export LANQIN_EXISTING_ACTION=3
assert_eq "3" "$(prompt_existing_install_action)" "existing install exit action"
export LANQIN_EXISTING_ACTION=4
assert_eq "4" "$(prompt_existing_install_action)" "existing install fresh action"
unset LANQIN_EXISTING_ACTION
} }
test_backup_reinstall_preserves_existing_directory() ( test_backup_reinstall_preserves_existing_directory() (
@@ -153,7 +145,7 @@ test_install_configuration 3 3 "80" "http://mail.example.com" "true"
test_nginx_configuration test_nginx_configuration
test_compose_configuration test_compose_configuration
test_legacy_configuration_is_preserved test_legacy_configuration_is_preserved
test_existing_install_action test_menu_choice
test_backup_reinstall_preserves_existing_directory test_backup_reinstall_preserves_existing_directory
printf 'install.sh tests passed\n' printf 'install.sh tests passed\n'