feat: add safe fresh reinstall option

This commit is contained in:
zxyszx
2026-08-03 23:23:41 +08:00
parent 800d482c77
commit b55c298ef4
3 changed files with 56 additions and 8 deletions
+3 -2
View File
@@ -36,8 +36,9 @@ bash <(curl -fsSL https://raw.githubusercontent.com/zxyszx/NewSzxcn-Email/main/i
``` ```
空白服务器会进入防火墙、邮件域名、管理员账号和 Web 部署方式的引导。检测到 空白服务器会进入防火墙、邮件域名、管理员账号和 Web 部署方式的引导。检测到
`/opt/newszxcn-email/.env` 时,脚本会先询问是更新现有邮局、修复现有安装还是退出 `/opt/newszxcn-email/.env` 时,脚本会先询问是更新现有邮局、修复现有安装退出
不会再直接跳过引导并重启服务。更新会先备份数据库,并在启动失败时自动回滚。 还是完整备份旧目录后全新安装;不会再直接跳过引导并重启服务。更新会先备份数据库,
并在启动失败时自动回滚。
脚本会自动完成: 脚本会自动完成:
+32 -6
View File
@@ -146,20 +146,20 @@ prompt_value() {
} }
prompt_choice() { prompt_choice() {
local variable="$1" prompt="$2" default_value="$3" value local variable="$1" prompt="$2" default_value="$3" max_value="${4:-3}" value
value="${!variable:-}" value="${!variable:-}"
while true; do while true; do
if [[ -z "${value}" ]] && has_tty; then if [[ -z "${value}" ]] && has_tty; then
read -r -p "${prompt}" value </dev/tty read -r -p "${prompt}" value </dev/tty
fi fi
value="${value:-${default_value}}" value="${value:-${default_value}}"
if [[ "${value}" =~ ^[123]$ ]]; then if [[ "${value}" =~ ^[0-9]+$ ]] && (( value >= 1 && value <= max_value )); then
printf '%s' "${value}" printf '%s' "${value}"
return return
fi fi
prompt_text "[提示] 请输入 1、2 或 3。\n" prompt_text "[提示] 请输入 1${max_value}。\n"
value="" value=""
has_tty || fail "${variable} 必须设置为 1、2 或 3" has_tty || fail "${variable} 必须设置为 1${max_value}"
done done
} }
@@ -170,11 +170,11 @@ prompt_existing_install_action() {
if [[ -n "${public_url}" ]]; then if [[ -n "${public_url}" ]]; then
prompt_text "[发现] 当前访问地址:${public_url}\n" prompt_text "[发现] 当前访问地址:${public_url}\n"
fi fi
prompt_text '请选择操作 [1]:\n1. 更新现有邮局(推荐,自动备份并支持回滚)\n2. 修复现有安装(保留配置和数据)\n3. 退出,不做任何修改\n' prompt_text '请选择操作 [1]:\n1. 更新现有邮局(推荐,自动备份并支持回滚)\n2. 修复现有安装(保留配置和数据)\n3. 退出,不做任何修改\n4. 备份现有安装并全新安装\n'
if [[ -z "${LANQIN_EXISTING_ACTION:-}" ]] && ! has_tty; then if [[ -z "${LANQIN_EXISTING_ACTION:-}" ]] && ! has_tty; then
fail "非交互环境不能选择已有安装操作;更新请执行 newszxcn-email update。" fail "非交互环境不能选择已有安装操作;更新请执行 newszxcn-email update。"
fi fi
action="$(prompt_choice LANQIN_EXISTING_ACTION "请选择 [1]: " "1")" action="$(prompt_choice LANQIN_EXISTING_ACTION "请选择 [1]: " "1" "4")"
printf '%s' "${action}" printf '%s' "${action}"
} }
@@ -612,6 +612,7 @@ do_install() {
1) do_update ;; 1) do_update ;;
2) do_repair_install ;; 2) do_repair_install ;;
3) success "已退出,现有邮局未作修改。" ;; 3) success "已退出,现有邮局未作修改。" ;;
4) do_backup_reinstall ;;
esac esac
return return
fi fi
@@ -723,6 +724,31 @@ do_uninstall() {
success "容器和自动生成的 Nginx 配置已移除,${INSTALL_DIR} 中的邮件、证书、配置和数据库仍然保留。" success "容器和自动生成的 Nginx 配置已移除,${INSTALL_DIR} 中的邮件、证书、配置和数据库仍然保留。"
} }
do_backup_reinstall() {
local backup_dir
backup_dir="${INSTALL_DIR}.backup-$(date -u +%Y%m%dT%H%M%SZ)"
if [[ -f "${INSTALL_DIR}/docker-compose.yml" ]] && command -v docker >/dev/null 2>&1; then
ensure_docker
compose down --remove-orphans
fi
if [[ -f "${NGINX_CONFIG}" ]]; then
rm -f "${NGINX_CONFIG}"
if command -v nginx >/dev/null 2>&1 && nginx -t >/dev/null 2>&1; then
if command -v systemctl >/dev/null 2>&1; then
systemctl reload nginx
else
nginx -s reload
fi
fi
fi
mv "${INSTALL_DIR}" "${backup_dir}"
success "旧安装已完整备份到 ${backup_dir}"
log "现在开始全新安装。"
do_install
}
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
+21
View File
@@ -122,9 +122,29 @@ test_existing_install_action() {
assert_eq "2" "$(prompt_existing_install_action)" "existing install repair action" assert_eq "2" "$(prompt_existing_install_action)" "existing install repair action"
export LANQIN_EXISTING_ACTION=3 export LANQIN_EXISTING_ACTION=3
assert_eq "3" "$(prompt_existing_install_action)" "existing install exit action" 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 unset LANQIN_EXISTING_ACTION
} }
test_backup_reinstall_preserves_existing_directory() (
local temp_dir backup_dir
temp_dir="$(mktemp -d)"
INSTALL_DIR="${temp_dir}/newszxcn-email"
NGINX_CONFIG="${temp_dir}/newszxcn-email.conf"
mkdir -p "${INSTALL_DIR}"
printf 'existing-data\n' > "${INSTALL_DIR}/marker"
do_install() {
[[ ! -e "${INSTALL_DIR}" ]] || fail_test "fresh install started before old directory was moved"
}
do_backup_reinstall
backup_dir="$(find "${temp_dir}" -maxdepth 1 -type d -name 'newszxcn-email.backup-*' -print -quit)"
[[ -n "${backup_dir}" ]] || fail_test "existing install backup directory missing"
grep -Fq 'existing-data' "${backup_dir}/marker" || fail_test "existing install data was not preserved"
)
test_hostname_validation test_hostname_validation
test_password_validation test_password_validation
test_install_configuration 1 1 "127.0.0.1:8088" "https://mail.example.com" "false" test_install_configuration 1 1 "127.0.0.1:8088" "https://mail.example.com" "false"
@@ -134,5 +154,6 @@ test_nginx_configuration
test_compose_configuration test_compose_configuration
test_legacy_configuration_is_preserved test_legacy_configuration_is_preserved
test_existing_install_action test_existing_install_action
test_backup_reinstall_preserves_existing_directory
printf 'install.sh tests passed\n' printf 'install.sh tests passed\n'