453 lines
14 KiB
Bash
453 lines
14 KiB
Bash
#!/usr/bin/env bash
|
||
# Nexus NX — 统一运维菜单(安装 + 升级 + 日常运维)
|
||
#
|
||
# nx 交互菜单
|
||
# nx update 一键更新(等同 deploy/update.sh)
|
||
# nx update --no-cache 无缓存重建镜像
|
||
# nx install-fresh 全新安装
|
||
# nx health 健康检查
|
||
#
|
||
set -euo pipefail
|
||
|
||
NX_SELF="$(readlink -f "${BASH_SOURCE[0]}")"
|
||
NX_DIR="$(cd "$(dirname "$NX_SELF")" && pwd)"
|
||
# shellcheck source=./nexus-1panel.sh
|
||
source "${NX_DIR}/nexus-1panel.sh"
|
||
|
||
pause_enter() {
|
||
echo ""
|
||
read -r -p "按 Enter 继续..."
|
||
}
|
||
|
||
clear_screen() {
|
||
if [[ -t 1 ]]; then
|
||
clear 2>/dev/null || true
|
||
fi
|
||
}
|
||
|
||
is_stack_installed() {
|
||
[[ -d "${NEXUS_ROOT}/.git" && -f "${NEXUS_ROOT}/${ENV_PROD}" ]]
|
||
}
|
||
|
||
detect_runtime() {
|
||
if is_stack_installed && docker compose version >/dev/null 2>&1; then
|
||
local prof q
|
||
prof="$(profile_env_file "$NEXUS_ROOT")"
|
||
if [[ -f "$prof" && -f "$NEXUS_ROOT/$ENV_PROD" ]]; then
|
||
local -a det_args=(-f "$COMPOSE_FILE")
|
||
if has_1panel_network && [[ -f "$NEXUS_ROOT/$COMPOSE_1PANEL" ]]; then
|
||
det_args+=(-f "$COMPOSE_1PANEL")
|
||
fi
|
||
q="$(cd "$NEXUS_ROOT" && docker compose "${det_args[@]}" \
|
||
--env-file "$ENV_PROD" --env-file "$prof" ps -q nexus 2>/dev/null || true)"
|
||
if [[ -n "$q" ]]; then
|
||
NX_RUNTIME="docker"
|
||
return 0
|
||
fi
|
||
fi
|
||
fi
|
||
if command -v supervisorctl >/dev/null 2>&1 && supervisorctl status nexus >/dev/null 2>&1; then
|
||
NX_RUNTIME="supervisor"
|
||
return 0
|
||
fi
|
||
NX_RUNTIME="none"
|
||
}
|
||
|
||
install_nx_cli() {
|
||
NEXUS_ROOT="$(cd "${NX_DIR}/.." && pwd)" \
|
||
bash "${NX_DIR}/install-nx-cli.sh" 2>/dev/null || true
|
||
}
|
||
|
||
menu_header() {
|
||
local title="$1"
|
||
clear_screen
|
||
echo ""
|
||
echo "╔══════════════════════════════════════════════════════════╗"
|
||
printf "║ Nexus NX — %-44s║\n" "$title"
|
||
echo "╠══════════════════════════════════════════════════════════╣"
|
||
echo "║ 域名: ${NEXUS_DOMAIN} 目录: ${NEXUS_ROOT}"
|
||
if is_stack_installed; then
|
||
detect_runtime
|
||
echo "║ 运行: ${NX_RUNTIME} 档位: $(cat "${NEXUS_ROOT}/${HOST_PROFILE_FILE}" 2>/dev/null || echo "${NEXUS_PROFILE}")"
|
||
if [[ -d "${NEXUS_ROOT}/.git" ]]; then
|
||
echo "║ 版本: $(git -C "${NEXUS_ROOT}" log -1 --oneline 2>/dev/null || echo '?')"
|
||
fi
|
||
else
|
||
echo "║ 状态: 未安装"
|
||
fi
|
||
echo "╚══════════════════════════════════════════════════════════╝"
|
||
echo ""
|
||
}
|
||
|
||
run_install() {
|
||
local profile="${1:-2c8g}" extra="${2:-}"
|
||
NEXUS_PROFILE="$profile"
|
||
SKIP_CLONE=false
|
||
FRESH_INSTALL=false
|
||
NO_BACKUP=false
|
||
PRUNE_IMAGES=false
|
||
FROM_ENV="${NEXUS_FROM_ENV}"
|
||
case "$extra" in
|
||
skip-clone) SKIP_CLONE=true ;;
|
||
fresh) FRESH_INSTALL=true ;;
|
||
from-env) FROM_ENV="${NEXUS_FROM_ENV}" ;;
|
||
esac
|
||
load_secrets
|
||
cmd_install
|
||
install_nx_cli
|
||
pause_enter
|
||
}
|
||
|
||
menu_install() {
|
||
while true; do
|
||
menu_header "安装"
|
||
cat <<'EOF'
|
||
[1] 一键安装(默认 2核8G / 2c8g)
|
||
[2] 安装 — 4核16G(4c16g)
|
||
[3] 安装 — 1核4G(1c4g)
|
||
[4] 自定义规格(输入 CPU 与内存 GB)
|
||
[5] 已 clone 到 /opt/nexus,仅 pull + 启动(--skip-clone)
|
||
[6] 全新安装(install-nexus-fresh.sh → /app/install.html)
|
||
[7] 检查端口 / 防火墙(不安装)
|
||
[0] 返回
|
||
|
||
EOF
|
||
read -r -p "请选择: " choice
|
||
case "$choice" in
|
||
1) run_install 2c8g "" ;;
|
||
2) run_install 4c16g "" ;;
|
||
3) run_install 1c4g "" ;;
|
||
4)
|
||
read -r -p "CPU 核数: " cpus
|
||
read -r -p "内存 GB: " mem
|
||
CUSTOM_CPUS="$cpus"
|
||
CUSTOM_MEM_GB="$mem"
|
||
run_install 2c8g ""
|
||
;;
|
||
5) run_install 2c8g skip-clone ;;
|
||
6)
|
||
if [[ -x "${NX_DIR}/install-nexus-fresh.sh" ]]; then
|
||
bash "${NX_DIR}/install-nexus-fresh.sh" --profile "${NEXUS_PROFILE:-2c8g}"
|
||
else
|
||
run_install 2c8g fresh
|
||
fi
|
||
;;
|
||
7)
|
||
require_root
|
||
load_secrets
|
||
check_ports_preflight "$NEXUS_ROOT"
|
||
if is_stack_installed; then
|
||
check_ports_post "$NEXUS_ROOT"
|
||
fi
|
||
pause_enter
|
||
;;
|
||
0) return 0 ;;
|
||
*) warn "无效选项" ; sleep 1 ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
ops_restart_nexus() {
|
||
step "重启 Nexus(Python)..."
|
||
if [[ "$NX_RUNTIME" == "docker" ]]; then
|
||
compose_cmd "$NEXUS_ROOT" restart nexus
|
||
elif [[ "$NX_RUNTIME" == "supervisor" ]]; then
|
||
supervisorctl restart nexus
|
||
else
|
||
error "未检测到运行中的 Nexus"
|
||
return 1
|
||
fi
|
||
sleep 3
|
||
verify_health "$NEXUS_ROOT" "重启" || true
|
||
}
|
||
|
||
ops_restart_stack() {
|
||
step "重启 Compose 栈..."
|
||
compose_cmd "$NEXUS_ROOT" restart
|
||
sleep 5
|
||
verify_health "$NEXUS_ROOT" "重启" || true
|
||
}
|
||
|
||
ops_stop_stack() {
|
||
warn "将停止 Nexus 容器(MySQL/Redis 为外置服务,不受影响)"
|
||
read -r -p "确认? [y/N]: " c
|
||
[[ "$c" == "y" || "$c" == "Y" ]] || return 0
|
||
compose_cmd "$NEXUS_ROOT" stop
|
||
info "已停止"
|
||
}
|
||
|
||
ops_start_stack() {
|
||
compose_cmd "$NEXUS_ROOT" up -d --remove-orphans
|
||
verify_nexus_1panel_network "$NEXUS_ROOT" || return 1
|
||
verify_health "$NEXUS_ROOT" "启动" || true
|
||
}
|
||
|
||
ops_logs() {
|
||
local svc="${1:-nexus}"
|
||
echo ""
|
||
read -r -p "跟踪日志行数 [80]: " n
|
||
n="${n:-80}"
|
||
compose_cmd "$NEXUS_ROOT" logs --tail="$n" -f "$svc"
|
||
}
|
||
|
||
ops_status() {
|
||
if [[ "$NX_RUNTIME" == "docker" ]]; then
|
||
compose_cmd "$NEXUS_ROOT" ps
|
||
elif [[ "$NX_RUNTIME" == "supervisor" ]]; then
|
||
supervisorctl status nexus
|
||
fi
|
||
echo ""
|
||
local port h code
|
||
port="$(nexus_publish_port "$NEXUS_ROOT")"
|
||
h=$(curl -sf "http://127.0.0.1:${port}/health" 2>/dev/null || echo "fail")
|
||
info "/health → ${h}"
|
||
local cname
|
||
cname="$(nexus_container_name 2>/dev/null || true)"
|
||
code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app/install.html" 2>/dev/null || echo "000")
|
||
if [[ -n "$cname" ]] && nexus_container_has_install_lock "$cname"; then
|
||
if [[ "$code" == "404" ]]; then
|
||
info "/app/install.html → 已归档 (HTTP 404)"
|
||
else
|
||
warn "/app/install.html → 应已归档但 HTTP ${code}"
|
||
fi
|
||
else
|
||
info "/app/install.html → HTTP ${code}"
|
||
fi
|
||
}
|
||
|
||
ops_backup_now() {
|
||
NO_BACKUP=false
|
||
backup_mysql "$NEXUS_ROOT"
|
||
}
|
||
|
||
ops_install_cron() {
|
||
bash "${NX_DIR}/install_ops_cron.sh"
|
||
}
|
||
|
||
ops_update() {
|
||
local args=()
|
||
PRUNE_IMAGES=false
|
||
NO_BACKUP=false
|
||
NO_CACHE=false
|
||
read -r -p "跳过 MySQL 备份? [y/N]: " nb
|
||
[[ "$nb" == "y" || "$nb" == "Y" ]] && args+=(--no-backup)
|
||
read -r -p "无缓存重建镜像? [y/N]: " nc
|
||
[[ "$nc" == "y" || "$nc" == "Y" ]] && args+=(--no-cache)
|
||
read -r -p "升级后清理悬空镜像? [y/N]: " pr
|
||
[[ "$pr" == "y" || "$pr" == "Y" ]] && args+=(--prune)
|
||
bash "${NX_DIR}/update.sh" "${args[@]}"
|
||
pause_enter
|
||
}
|
||
|
||
require_installed() {
|
||
if ! is_stack_installed; then
|
||
warn "尚未安装,请先使用菜单 [1] 安装"
|
||
pause_enter
|
||
return 1
|
||
fi
|
||
require_root
|
||
load_secrets
|
||
load_saved_profile "$NEXUS_ROOT"
|
||
detect_runtime
|
||
return 0
|
||
}
|
||
|
||
menu_main() {
|
||
load_secrets
|
||
while true; do
|
||
menu_header "运维菜单"
|
||
cat <<'EOF'
|
||
── 安装 ──
|
||
[1] 安装向导(新机 / 迁机 / 档位)
|
||
[2] 全新安装 → /app/install.html
|
||
|
||
── 日常运维(需已安装)──
|
||
[3] 状态 + 健康检查
|
||
[4] 一键更新(pull + 重建镜像)★
|
||
[5] 重启 Nexus(Python)
|
||
[6] 查看 Nexus 日志
|
||
[7] 启动 / 停止 Compose 栈
|
||
[8] 重建镜像并启动(--build nexus)
|
||
[9] 备份 MySQL
|
||
[e] 安装巡检/备份 cron(health_monitor + db_backup)
|
||
[v] 1Panel 安装/运维验收脚本
|
||
|
||
── 其它 ──
|
||
[a] 检查 Git 是否有更新
|
||
[b] 端口 / 防火墙检查
|
||
[c] 进入 Nexus 容器 shell
|
||
[d] 1Panel 反代说明
|
||
[0] 退出
|
||
|
||
EOF
|
||
read -r -p "请选择: " choice
|
||
case "$choice" in
|
||
1) menu_install ;;
|
||
2)
|
||
require_root
|
||
if is_stack_installed; then
|
||
warn "将执行全新安装(清除 nexus-state 卷内旧配置并重建容器)"
|
||
read -r -p "确认继续? [y/N]: " fresh_ok
|
||
[[ "$fresh_ok" == "y" || "$fresh_ok" == "Y" ]] || continue
|
||
fi
|
||
exec bash "${NX_DIR}/install-nexus-fresh.sh" --profile "${NEXUS_PROFILE:-2c8g}"
|
||
;;
|
||
3)
|
||
require_root
|
||
if is_stack_installed; then
|
||
load_saved_profile "$NEXUS_ROOT"
|
||
detect_runtime
|
||
ops_status
|
||
else
|
||
check_ports_preflight "$NEXUS_ROOT"
|
||
fi
|
||
pause_enter
|
||
;;
|
||
4)
|
||
require_installed || continue
|
||
ops_update
|
||
;;
|
||
5)
|
||
require_installed || continue
|
||
ops_restart_nexus
|
||
pause_enter
|
||
;;
|
||
6)
|
||
require_installed || continue
|
||
ops_logs nexus
|
||
;;
|
||
7)
|
||
require_installed || continue
|
||
echo " [1] 启动 [2] 停止 [3] 重启栈"
|
||
read -r -p "选: " s
|
||
case "$s" in
|
||
1) ops_start_stack ;;
|
||
2) ops_stop_stack ;;
|
||
3) ops_restart_stack ;;
|
||
*) warn "无效" ;;
|
||
esac
|
||
pause_enter
|
||
;;
|
||
8)
|
||
require_installed || continue
|
||
read -r -p "无缓存重建? [y/N]: " nc
|
||
if [[ "$nc" == "y" || "$nc" == "Y" ]]; then
|
||
compose_cmd "$NEXUS_ROOT" build --no-cache nexus
|
||
fi
|
||
compose_cmd "$NEXUS_ROOT" up -d --build --remove-orphans nexus
|
||
verify_nexus_1panel_network "$NEXUS_ROOT" || true
|
||
verify_health "$NEXUS_ROOT" "重建" || true
|
||
pause_enter
|
||
;;
|
||
9)
|
||
require_installed || continue
|
||
ops_backup_now
|
||
pause_enter
|
||
;;
|
||
e|E)
|
||
require_root
|
||
ops_install_cron
|
||
pause_enter
|
||
;;
|
||
v|V)
|
||
require_root
|
||
bash "${NX_DIR}/verify-1panel-install-wizard.sh"
|
||
pause_enter
|
||
;;
|
||
a|A)
|
||
require_root
|
||
cmd_check
|
||
pause_enter
|
||
;;
|
||
b|B)
|
||
require_root
|
||
check_ports_preflight "$NEXUS_ROOT"
|
||
is_stack_installed && check_ports_post "$NEXUS_ROOT" || true
|
||
pause_enter
|
||
;;
|
||
c|C)
|
||
require_installed || continue
|
||
compose_cmd "$NEXUS_ROOT" exec nexus bash || compose_cmd "$NEXUS_ROOT" exec nexus sh
|
||
;;
|
||
d|D)
|
||
info "1Panel → 网站 → 创建站点 ${NEXUS_DOMAIN}"
|
||
info "反向代理: http://127.0.0.1:$(nexus_publish_port "$NEXUS_ROOT")"
|
||
info "示例: ${NEXUS_ROOT}/deploy/1panel/openresty-nexus.conf.example"
|
||
pause_enter
|
||
;;
|
||
0) exit 0 ;;
|
||
*) warn "无效选项"; sleep 1 ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
nx_main() {
|
||
local sub="${1:-}"
|
||
install_nx_cli
|
||
load_secrets
|
||
case "$sub" in
|
||
""|menu|god|ops|admin)
|
||
# god/ops 已合并进主菜单,保留别名兼容
|
||
[[ "$sub" == "god" || "$sub" == "ops" || "$sub" == "admin" ]] && \
|
||
warn "「上帝菜单」已合并进 nx 主菜单,直接选对应项即可"
|
||
menu_main
|
||
;;
|
||
install) menu_install ;;
|
||
update|upgrade)
|
||
require_root
|
||
shift || true
|
||
exec bash "${NX_DIR}/update.sh" "$@"
|
||
;;
|
||
install-fresh|fresh)
|
||
require_root
|
||
exec bash "${NX_DIR}/install-nexus-fresh.sh" "$@"
|
||
;;
|
||
install-auto|auto)
|
||
require_root
|
||
if is_stack_installed; then
|
||
warn "已安装,install-auto 将执行升级而非重复安装"
|
||
exec bash "${NX_DIR}/update.sh"
|
||
else
|
||
run_install "${NEXUS_PROFILE:-2c8g}" ""
|
||
fi
|
||
;;
|
||
health)
|
||
require_root
|
||
is_stack_installed && ops_status || check_ports_preflight "$NEXUS_ROOT"
|
||
;;
|
||
cron)
|
||
require_root
|
||
ops_install_cron
|
||
;;
|
||
verify)
|
||
require_root
|
||
bash "${NX_DIR}/verify-1panel-install-wizard.sh"
|
||
;;
|
||
-h|--help)
|
||
cat <<EOF
|
||
用法: nx [子命令]
|
||
|
||
(无) 统一运维菜单(安装 + 升级 + 重启 + 日志)
|
||
update 一键更新(等同 bash deploy/update.sh)
|
||
install-fresh 全新安装(/app/install.html)
|
||
install-auto 非交互安装(默认 2c8g)
|
||
health 健康检查
|
||
cron 安装 health_monitor + db_backup crontab
|
||
verify 运行 1Panel 验收脚本
|
||
|
||
god / ops 已合并进主菜单(兼容别名)
|
||
|
||
curl 入口:
|
||
安装 curl -fsSL .../deploy/quick-install.sh | bash
|
||
更新 curl -fsSL .../deploy/update.sh | bash
|
||
EOF
|
||
;;
|
||
*)
|
||
main "$@"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
chmod +x "${NX_DIR}/nx" 2>/dev/null || true
|
||
nx_main "$@"
|