536d520744
readlink -f fixes sourcing nexus-1panel.sh; register nexus-1panel global cmd. Co-authored-by: Cursor <cursoragent@cursor.com>
392 lines
12 KiB
Bash
392 lines
12 KiB
Bash
#!/usr/bin/env bash
|
||
# Nexus NX — 一键安装 + 上帝菜单(1Panel Docker 生产栈)
|
||
#
|
||
# bash deploy/nx 交互菜单
|
||
# bash deploy/nx install 同菜单「安装」
|
||
# bash deploy/nx god 上帝菜单(运维)
|
||
# nx 安装后链到 /usr/local/bin/nx
|
||
#
|
||
set -euo pipefail
|
||
|
||
# 经 /usr/local/bin/nx 符号链接调用时,须 readlink 才能找到 deploy/ 目录
|
||
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
|
||
q="$(cd "$NEXUS_ROOT" && docker compose -f "$COMPOSE_FILE" \
|
||
--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() {
|
||
if [[ "$(id -u)" -ne 0 ]]; then
|
||
return 0
|
||
fi
|
||
ln -sf "${NX_DIR}/nx" /usr/local/bin/nx 2>/dev/null || true
|
||
chmod +x "${NX_DIR}/nx" 2>/dev/null || true
|
||
}
|
||
|
||
menu_header() {
|
||
local title="$1"
|
||
clear_screen
|
||
echo ""
|
||
echo "╔══════════════════════════════════════════════════════════╗"
|
||
echo "║ Nexus NX — ${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] 检查端口 / 防火墙(不安装)
|
||
[8] 写入 Gitea 令牌(init-token)
|
||
[9] 返回主菜单
|
||
[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
|
||
;;
|
||
8)
|
||
read -r -p "Gitea Access Token: " tok
|
||
cmd_init_token "$tok"
|
||
pause_enter
|
||
;;
|
||
9) return 0 ;;
|
||
0) exit 0 ;;
|
||
*) warn "无效选项" ; sleep 1 ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
god_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
|
||
info "已发送重启"
|
||
sleep 3
|
||
curl -sf "http://127.0.0.1:$(nexus_publish_port "$NEXUS_ROOT")/health" && info "/health → ok" || warn "健康检查未通过,请查看日志"
|
||
}
|
||
|
||
god_restart_stack() {
|
||
step "重启全部 Compose 服务..."
|
||
compose_cmd "$NEXUS_ROOT" restart
|
||
sleep 5
|
||
verify_health "$NEXUS_ROOT" "重启" || true
|
||
}
|
||
|
||
god_stop_stack() {
|
||
warn "将停止 MySQL / Redis / Nexus 全部容器"
|
||
read -r -p "确认? [y/N]: " c
|
||
[[ "$c" == "y" || "$c" == "Y" ]] || return 0
|
||
compose_cmd "$NEXUS_ROOT" stop
|
||
info "已停止"
|
||
}
|
||
|
||
god_start_stack() {
|
||
compose_cmd "$NEXUS_ROOT" up -d
|
||
verify_health "$NEXUS_ROOT" "启动" || true
|
||
}
|
||
|
||
god_logs() {
|
||
local svc="${1:-nexus}"
|
||
echo ""
|
||
read -r -p "跟踪日志行数 [80]: " n
|
||
n="${n:-80}"
|
||
compose_cmd "$NEXUS_ROOT" logs --tail="$n" -f "$svc"
|
||
}
|
||
|
||
god_status() {
|
||
if [[ "$NX_RUNTIME" == "docker" ]]; then
|
||
compose_cmd "$NEXUS_ROOT" ps
|
||
elif [[ "$NX_RUNTIME" == "supervisor" ]]; then
|
||
supervisorctl status nexus
|
||
fi
|
||
echo ""
|
||
local port h
|
||
port="$(nexus_publish_port "$NEXUS_ROOT")"
|
||
h=$(curl -sf "http://127.0.0.1:${port}/health" 2>/dev/null || echo "fail")
|
||
info "/health → ${h}"
|
||
curl -s -o /dev/null -w " /app/ HTTP %{http_code}\n" "http://127.0.0.1:${port}/app/" 2>/dev/null || true
|
||
}
|
||
|
||
god_backup_now() {
|
||
NO_BACKUP=false
|
||
backup_mysql "$NEXUS_ROOT"
|
||
}
|
||
|
||
god_upgrade_interactive() {
|
||
PRUNE_IMAGES=false
|
||
NO_BACKUP=false
|
||
read -r -p "跳过 MySQL 备份? [y/N]: " nb
|
||
[[ "$nb" == "y" || "$nb" == "Y" ]] && NO_BACKUP=true
|
||
read -r -p "升级后清理悬空镜像? [y/N]: " pr
|
||
[[ "$pr" == "y" || "$pr" == "Y" ]] && PRUNE_IMAGES=true
|
||
cmd_upgrade
|
||
pause_enter
|
||
}
|
||
|
||
menu_god() {
|
||
if ! is_stack_installed; then
|
||
warn "尚未安装,请先使用安装向导"
|
||
pause_enter
|
||
return 0
|
||
fi
|
||
require_root
|
||
load_secrets
|
||
load_saved_profile "$NEXUS_ROOT"
|
||
detect_runtime
|
||
|
||
while true; do
|
||
menu_header "上帝菜单 · 运维"
|
||
cat <<EOF
|
||
── 服务 ──
|
||
[1] 重启 Nexus(Python 应用)
|
||
[2] 重启全部容器(mysql + redis + nexus)
|
||
[3] 启动 Compose 栈
|
||
[4] 停止 Compose 栈
|
||
[5] 重建 Nexus 镜像并启动(up -d --build nexus)
|
||
|
||
── 观测 ──
|
||
[6] 状态 + 健康检查
|
||
[7] 查看 Nexus 日志(跟踪)
|
||
[8] 查看 MySQL / Redis 日志
|
||
[9] 端口 / 防火墙检查
|
||
|
||
── 发布 ──
|
||
[a] 检查 Git 更新
|
||
[b] 一键升级(备份 + pull + 重建)
|
||
[c] 立即备份 MySQL
|
||
|
||
── 其他 ──
|
||
[d] 进入 Nexus 容器 shell
|
||
[e] 打开 1Panel 反代说明
|
||
[0] 返回主菜单
|
||
|
||
EOF
|
||
read -r -p "请选择: " choice
|
||
case "$choice" in
|
||
1) god_restart_nexus; pause_enter ;;
|
||
2) god_restart_stack; pause_enter ;;
|
||
3) god_start_stack; pause_enter ;;
|
||
4) god_stop_stack; pause_enter ;;
|
||
5)
|
||
compose_cmd "$NEXUS_ROOT" up -d --build nexus
|
||
verify_health "$NEXUS_ROOT" "重建" || true
|
||
pause_enter
|
||
;;
|
||
6) god_status; pause_enter ;;
|
||
7) god_logs nexus ;;
|
||
8)
|
||
echo "1) mysql 2) redis"
|
||
read -r -p "选: " s
|
||
[[ "$s" == "1" ]] && god_logs mysql || god_logs redis
|
||
;;
|
||
9)
|
||
check_ports_preflight "$NEXUS_ROOT"
|
||
check_ports_post "$NEXUS_ROOT"
|
||
pause_enter
|
||
;;
|
||
a|A)
|
||
cmd_check
|
||
pause_enter
|
||
;;
|
||
b|B) god_upgrade_interactive ;;
|
||
c|C) god_backup_now; pause_enter ;;
|
||
d)
|
||
compose_cmd "$NEXUS_ROOT" exec nexus bash || compose_cmd "$NEXUS_ROOT" exec nexus sh
|
||
;;
|
||
e)
|
||
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) return 0 ;;
|
||
*) warn "无效选项"; sleep 1 ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
menu_main() {
|
||
load_secrets
|
||
while true; do
|
||
menu_header "主菜单"
|
||
if is_stack_installed; then
|
||
echo " 提示: 安装完成后请用 [2] 上帝菜单 重启 Python / 升级 / 看日志"
|
||
echo ""
|
||
fi
|
||
cat <<'EOF'
|
||
[1] 安装向导(新机 / 迁机)
|
||
[2] 上帝菜单(重启 Python · 升级 · 日志 · 备份)
|
||
[3] 快速健康检查
|
||
[0] 退出
|
||
|
||
EOF
|
||
read -r -p "请选择: " choice
|
||
case "$choice" in
|
||
1) menu_install ;;
|
||
2) menu_god ;;
|
||
3)
|
||
require_root
|
||
if is_stack_installed; then
|
||
god_status
|
||
else
|
||
check_ports_preflight "$NEXUS_ROOT"
|
||
fi
|
||
pause_enter
|
||
;;
|
||
0) exit 0 ;;
|
||
*) warn "无效选项"; sleep 1 ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
nx_main() {
|
||
local sub="${1:-}"
|
||
load_secrets
|
||
case "$sub" in
|
||
""|menu) menu_main ;;
|
||
install) menu_install ;;
|
||
god|ops|admin) menu_god ;;
|
||
install-fresh|fresh)
|
||
require_root
|
||
exec bash "${NX_DIR}/install-nexus-fresh.sh" "$@"
|
||
;;
|
||
install-auto|auto)
|
||
require_root
|
||
if is_stack_installed; then
|
||
run_install "${NEXUS_PROFILE:-2c8g}" skip-clone
|
||
else
|
||
run_install "${NEXUS_PROFILE:-2c8g}" ""
|
||
fi
|
||
;;
|
||
health)
|
||
require_root
|
||
is_stack_installed && god_status || check_ports_preflight "$NEXUS_ROOT"
|
||
;;
|
||
-h|--help)
|
||
cat <<EOF
|
||
用法: nx [子命令]
|
||
|
||
(无) 交互主菜单
|
||
install 安装向导
|
||
install-fresh 全新安装(1Panel 风格,/app/install.html)
|
||
install-auto 非交互安装已有密钥(2c8g)
|
||
god 上帝菜单(重启/升级/日志)
|
||
health 健康检查
|
||
|
||
底层仍可使用: bash deploy/nexus-1panel.sh install|upgrade|check|ports
|
||
EOF
|
||
;;
|
||
*)
|
||
# 兼容 nexus-1panel.sh 子命令
|
||
main "$@"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
chmod +x "${NX_DIR}/nx" 2>/dev/null || true
|
||
nx_main "$@"
|