Files
Nexus/deploy/verify-1panel-install-wizard.sh
2026-07-08 22:31:31 +08:00

438 lines
17 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Verify 1Panel + Docker install wizard readiness (run on the VPS as root).
# See: deploy/README-1panel.md
set -euo pipefail
NEXUS_ROOT="${NEXUS_ROOT:-/opt/nexus}"
ENV_PROD="${NEXUS_ROOT}/docker/.env.prod"
COMPOSE_FILE="${NEXUS_ROOT}/docker/docker-compose.prod.yml"
COMPOSE_1PANEL="${NEXUS_ROOT}/docker/docker-compose.1panel.yml"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
pass() { echo -e "${GREEN}[PASS]${NC} $*"; }
fail() { echo -e "${RED}[FAIL]${NC} $*"; FAILURES=$((FAILURES + 1)); }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; WARNS=$((WARNS + 1)); }
info() { echo -e " $*"; }
FAILURES=0
WARNS=0
INSTALL_WIZARD_ARCHIVED=false
PUBLISH_PORT=8600
require_root() {
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
fail "请用 root 运行(1Panel 终端默认 root"
exit 1
fi
}
resolve_nexus_container() {
NEXUS_CONTAINER="$(docker ps --format '{{.Names}}' | grep -E 'nexus-prod-nexus|nexus-nexus-1' | head -1 || true)"
if [[ -z "$NEXUS_CONTAINER" ]]; then
fail "未找到运行中的 Nexus 容器"
info "修复: cd $NEXUS_ROOT && nx update --no-cache"
return 1
fi
pass "Nexus 容器: $NEXUS_CONTAINER"
}
check_repo() {
if [[ ! -d "$NEXUS_ROOT/.git" ]]; then
fail "仓库不存在: $NEXUS_ROOT"
return 1
fi
local sha msg
sha="$(git -C "$NEXUS_ROOT" rev-parse --short HEAD 2>/dev/null || echo unknown)"
msg="$(git -C "$NEXUS_ROOT" log -1 --oneline 2>/dev/null || true)"
pass "Git: $msg"
if git -C "$NEXUS_ROOT" merge-base --is-ancestor b25d079 HEAD 2>/dev/null; then
pass "已包含 b25d0791Panel 容器名 sync 修复)"
else
warn "早于 b25d079 — 步骤 3 可能仍预填 host.docker.internal"
info "修复: cd $NEXUS_ROOT && nx update --no-cache"
fi
if git -C "$NEXUS_ROOT" merge-base --is-ancestor 3b2856f HEAD 2>/dev/null; then
pass "已包含 3b2856f+Redis :pass@ 自动解析,无 root"
else
warn "早于 3b2856f — Redis 认证可能仍误用 root"
info "修复: cd $NEXUS_ROOT && git pull && bash deploy/sync-install-wizard-to-container.sh"
fi
}
check_env_prod() {
if [[ ! -f "$ENV_PROD" ]]; then
fail "缺少 $ENV_PROD"
info "修复: bash $NEXUS_ROOT/deploy/install-nexus-fresh.sh --skip-clone"
return 1
fi
pass "docker/.env.prod 存在"
PUBLISH_PORT="$(grep -E '^NEXUS_PUBLISH_PORT=' "$ENV_PROD" 2>/dev/null | cut -d= -f2- | tr -d '\r" ' || true)"
PUBLISH_PORT="${PUBLISH_PORT:-8600}"
pass "NEXUS_PUBLISH_PORT=$PUBLISH_PORT"
if grep -qE '^NEXUS_INSTALL_WIZARD_PENDING=1' "$ENV_PROD"; then
pass "NEXUS_INSTALL_WIZARD_PENDING=1(安装模式)"
else
warn "NEXUS_INSTALL_WIZARD_PENDING≠1 — entrypoint 可能已写 /app/.env"
fi
local db redis
db="$(grep -E '^NEXUS_1PANEL_DB_HOST=' "$ENV_PROD" 2>/dev/null | cut -d= -f2- || true)"
redis="$(grep -E '^NEXUS_1PANEL_REDIS_HOST=' "$ENV_PROD" 2>/dev/null | cut -d= -f2- || true)"
if [[ -n "$db" ]]; then
pass "NEXUS_1PANEL_DB_HOST=$db"
else
fail "未设置 NEXUS_1PANEL_DB_HOST"
info "修复: bash $NEXUS_ROOT/deploy/detect-1panel-services.sh && nx update"
fi
if [[ -n "$redis" ]]; then
pass "NEXUS_1PANEL_REDIS_HOST=$redis"
local rurl
rurl="$(grep -E '^NEXUS_REDIS_URL=' "$ENV_PROD" 2>/dev/null | cut -d= -f2- || true)"
if [[ "$rurl" == *"$redis"* ]]; then
pass "NEXUS_REDIS_URL 已指向 Redis 容器"
else
warn "NEXUS_REDIS_URL 未含 $redis — nx update 会同步"
fi
else
fail "未设置 NEXUS_1PANEL_REDIS_HOST"
info "修复: 在 1Panel 应用商店安装 Redis 后执行 nx update"
fi
}
check_1panel_network() {
if ! docker network inspect 1panel-network >/dev/null 2>&1; then
fail "1panel-network 不存在"
info "修复: 在 1Panel 应用商店安装 MySQL + Redis(会自动创建网络)"
return 1
fi
pass "1panel-network 存在"
if [[ -n "${NEXUS_CONTAINER:-}" ]]; then
if docker inspect "$NEXUS_CONTAINER" --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' \
| grep -q '1panel-network'; then
pass "Nexus 已接入 1panel-network"
else
fail "Nexus 未接入 1panel-network"
info "修复: cd $NEXUS_ROOT && nx update --no-cache"
fi
fi
if [[ -x "$NEXUS_ROOT/deploy/detect-1panel-services.sh" ]]; then
info "探测结果:"
"$NEXUS_ROOT/deploy/detect-1panel-services.sh" | sed 's/^/ /' || true
fi
}
probe_from_container() {
[[ -n "${NEXUS_CONTAINER:-}" ]] || return 0
local db redis
db="$(grep -E '^NEXUS_1PANEL_DB_HOST=' "$ENV_PROD" 2>/dev/null | cut -d= -f2- || true)"
redis="$(grep -E '^NEXUS_1PANEL_REDIS_HOST=' "$ENV_PROD" 2>/dev/null | cut -d= -f2- || true)"
if [[ -n "$db" ]]; then
if docker exec "$NEXUS_CONTAINER" python3 -c "
import socket
s=socket.socket(); s.settimeout(3); s.connect(('$db', 3306)); s.close()
" 2>/dev/null; then
pass "容器内 TCP → MySQL $db:3306"
else
fail "容器内无法连接 MySQL $db:3306"
info "检查 MySQL 容器是否运行、Nexus 是否在 1panel-network"
fi
fi
if [[ -n "$redis" ]]; then
if docker exec "$NEXUS_CONTAINER" python3 -c "
import socket
s=socket.socket(); s.settimeout(3); s.connect(('$redis', 6379)); s.close()
" 2>/dev/null; then
pass "容器内 TCP → Redis $redis:6379"
else
fail "容器内无法连接 Redis $redis:6379"
fi
fi
}
check_app_env_redis_url() {
[[ -n "${NEXUS_CONTAINER:-}" ]] || return 0
if ! docker exec "$NEXUS_CONTAINER" test -f /app/.env 2>/dev/null; then
return 0
fi
local rurl
rurl="$(docker exec "$NEXUS_CONTAINER" grep -E '^NEXUS_REDIS_URL=' /app/.env 2>/dev/null | cut -d= -f2- | tr -d '"' || true)"
if [[ -z "$rurl" ]]; then
warn "/app/.env 无 NEXUS_REDIS_URL"
return 0
fi
if [[ "$rurl" == *"://root:"* ]]; then
fail "NEXUS_REDIS_URL 含 root 用户(1Panel Redis 无 root 账号)"
info "修复: redis://:密码@1Panel-redis-xxxx:6379/0 — 见 deploy/test-1panel-redis.sh"
elif [[ "$rurl" == *"1Panel-redis"* ]] || [[ "$rurl" == *"://:"* ]]; then
pass "NEXUS_REDIS_URL 格式合理(容器名或 :pass@"
else
warn "NEXUS_REDIS_URL 可能未指向 1Panel Redis: $rurl"
fi
}
check_install_mode() {
[[ -n "${NEXUS_CONTAINER:-}" ]] || return 0
if docker exec "$NEXUS_CONTAINER" test -f /app/.env 2>/dev/null; then
warn "/app/.env 已存在 — 安装向导可能已进入步骤 3+ 或误写 env"
check_app_env_redis_url
info "若需重来: 删卷 nexus-state 中 .env 并设 NEXUS_INSTALL_WIZARD_PENDING=1 后 nx update"
else
pass "/app/.env 不存在(安装模式 OK"
fi
if docker exec "$NEXUS_CONTAINER" test -f /app/.install_locked 2>/dev/null \
|| docker exec "$NEXUS_CONTAINER" test -f /var/lib/nexus/.install_locked 2>/dev/null \
|| docker exec "$NEXUS_CONTAINER" test -f /app/web/app/install.html.bak 2>/dev/null; then
pass "安装向导已锁定/已归档 — 应访问 /app/ 登录"
INSTALL_WIZARD_ARCHIVED=true
fi
local hosts_json
hosts_json="$(docker exec "$NEXUS_CONTAINER" cat /app/web/data/1panel-hosts.json 2>/dev/null || true)"
if [[ -n "$hosts_json" ]]; then
pass "1panel-hosts.json: $hosts_json"
else
warn "无 1panel-hosts.json(依赖容器环境变量,nx update 会写入)"
fi
}
check_ops_cron() {
if [[ "${INSTALL_WIZARD_ARCHIVED:-false}" != true ]]; then
return 0
fi
if ! command -v crontab >/dev/null 2>&1; then
warn "未安装 cron/crontab — Layer 3 宿主机巡检不可用"
info "修复: sudo bash $NEXUS_ROOT/deploy/install_ops_cron.sh"
return 0
fi
local cron
cron="$(crontab -l 2>/dev/null || true)"
if echo "$cron" | grep -q 'nexus-health-monitor\|deploy/health_monitor.sh'; then
pass "Crontab 含 health_monitor"
else
warn "Crontab 未配置 health_monitorLayer 3 巡检)"
info "修复: sudo nx cron"
fi
if echo "$cron" | grep -q 'nexus-mysql-backup\|deploy/db_backup.sh'; then
pass "Crontab 含 db_backup"
else
warn "Crontab 未配置 db_backup(每日 MySQL 备份)"
info "修复: sudo nx cron"
fi
}
check_mysql_backup() {
if [[ "${INSTALL_WIZARD_ARCHIVED:-false}" != true ]]; then
return 0
fi
local dump_sh="${NEXUS_ROOT}/deploy/mysql_dump_to_file.sh"
if [[ -f "$dump_sh" ]]; then
pass "mysql_dump_to_file.sh 存在"
else
warn "缺少 mysql_dump_to_file.sh — 升级前无法备份 MySQL"
info "修复: cd $NEXUS_ROOT && git pull && nx update"
return 0
fi
if [[ -d /var/backups/nexus ]] && ls /var/backups/nexus/nexus_*.sql.gz >/dev/null 2>&1; then
local latest
latest="$(ls -t /var/backups/nexus/nexus_*.sql.gz 2>/dev/null | head -1)"
pass "已有 MySQL 备份: $(basename "$latest")"
else
warn "尚无 MySQL 备份文件(/var/backups/nexus"
info "可手动: sudo bash $NEXUS_ROOT/deploy/db_backup.sh"
fi
}
check_http_local() {
local health code
health="$(curl -sf "http://127.0.0.1:${PUBLISH_PORT}/health" 2>/dev/null || true)"
if [[ "$health" == "ok" ]]; then
pass "/health → ok"
else
fail "/health 非 ok(当前: ${health:-无响应}"
info "修复: docker logs --tail=80 $NEXUS_CONTAINER"
return 1
fi
code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${PUBLISH_PORT}/app/install.html" 2>/dev/null || echo 000)"
if [[ "${INSTALL_WIZARD_ARCHIVED:-false}" == true ]]; then
if [[ "$code" == "404" ]]; then
pass "/app/install.html 已归档 → HTTP 404"
else
fail "/app/install.html 应已归档但 HTTP $code"
info "修复: nx update(会执行 archive_install_wizard_in_container"
fi
return 0
fi
if [[ "$code" == "200" ]]; then
pass "/app/install.html → HTTP 200"
else
fail "/app/install.html → HTTP $code"
info "修复: bash $NEXUS_ROOT/deploy/sync-install-wizard-to-container.sh"
fi
}
check_install_api() {
if [[ "${INSTALL_WIZARD_ARCHIVED:-false}" == true ]]; then
pass "安装 API 已关闭(向导已锁定,/api/install/* 返回 403 为预期)"
return 0
fi
local status env_json
status="$(curl -sf "http://127.0.0.1:${PUBLISH_PORT}/api/install/status" 2>/dev/null || true)"
if [[ -n "$status" ]]; then
pass "/api/install/status → $status"
if echo "$status" | grep -q '"installed":true'; then
warn "installed=true — 向导已完成,请访问 /app/ 登录"
fi
else
fail "/api/install/status 无响应"
fi
env_json="$(curl -sf "http://127.0.0.1:${PUBLISH_PORT}/api/install/env-check" 2>/dev/null || true)"
if [[ -z "$env_json" ]]; then
fail "/api/install/env-check 无响应"
return 1
fi
if echo "$env_json" | grep -q 'docker_defaults'; then
local db_host
db_host="$(echo "$env_json" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print((d.get('docker_defaults') or {}).get('db_host',''))
" 2>/dev/null || true)"
if [[ -n "$db_host" && "$db_host" != "host.docker.internal" ]]; then
pass "步骤 3 预填 db_host=$db_host"
elif [[ "$db_host" == "host.docker.internal" ]]; then
fail "步骤 3 仍预填 host.docker.internal"
info "修复: nx update --no-cache(需 b25d079+"
fi
else
warn "env-check 无 docker_defaults(非 Docker 模式?)"
fi
if echo "$env_json" | grep -q '1Panel-mysql'; then
pass "env-check MySQL 探测含 1Panel 容器名"
elif echo "$env_json" | grep -q 'host.docker.internal'; then
warn "env-check MySQL 仍指向 host.docker.internal"
fi
}
check_container_image_features() {
[[ -n "${NEXUS_CONTAINER:-}" ]] || return 0
local host_sha py_ok html_file html_ok host_root_ok
host_sha="$(git -C "$NEXUS_ROOT" rev-parse --short HEAD 2>/dev/null || echo unknown)"
pass "宿主机 Git: $host_sha"
if docker exec "$NEXUS_CONTAINER" grep -q '_host_deploy_root' /app/server/api/install.py 2>/dev/null; then
pass "容器 install.py 含 host_deploy_rootround6+"
py_ok=1
else
warn "容器 install.py 较旧 — 需 nx update 重建镜像"
fi
html_file="/app/web/app/install.html"
if docker exec "$NEXUS_CONTAINER" test -f /app/web/app/install.html.bak 2>/dev/null; then
html_file="/app/web/app/install.html.bak"
fi
if docker exec "$NEXUS_CONTAINER" grep -q 'install_ops_cron' "$html_file" 2>/dev/null; then
pass "容器 install.html 含 Layer 3 cron 指引(round7+"
html_ok=1
else
warn "容器 install.html 较旧 — 需 nx update 重建镜像"
fi
host_root_ok="$(docker exec "$NEXUS_CONTAINER" printenv NEXUS_HOST_ROOT 2>/dev/null || true)"
if [[ -n "$host_root_ok" ]]; then
pass "NEXUS_HOST_ROOT=$host_root_ok"
else
warn "容器未注入 NEXUS_HOST_ROOT — nx update 重建后可修复"
fi
if [[ -n "${py_ok:-}" && -n "${html_ok:-}" ]]; then
info "镜像与宿主机代码特性对齐(重建后验收)"
else
info "修复: cd $NEXUS_ROOT && sudo nx update"
fi
}
check_openresty_hint() {
local domain code
domain="$(grep -E '^NEXUS_API_BASE_URL=' "$ENV_PROD" 2>/dev/null | sed 's|.*https\?://||;s|/.*||' || echo api.synaglobal.vip)"
code="$(curl -sk -o /dev/null -w '%{http_code}' --connect-timeout 5 "https://${domain}/health" 2>/dev/null || echo 000)"
if [[ "$code" == "200" ]] && curl -sk "https://${domain}/health" 2>/dev/null | grep -q '^ok$'; then
pass "HTTPS https://${domain}/health → ok"
else
warn "HTTPS https://${domain}/health → HTTP $code(外网可能未配 OpenResty 反代)"
info "1Panel → 网站 → 反代 http://127.0.0.1:8600"
info "参考: $NEXUS_ROOT/deploy/1panel/openresty-nexus.conf.example"
fi
}
print_next_steps() {
local domain
domain="$(grep -E '^NEXUS_API_BASE_URL=' "$ENV_PROD" 2>/dev/null | sed 's|.*https\?://||;s|/.*||' || echo '你的域名')"
echo ""
if [[ "${INSTALL_WIZARD_ARCHIVED:-false}" == true ]]; then
echo "=========================================="
echo " 已安装 — 日常运维"
echo "=========================================="
echo " 登录: https://${domain}/app/"
echo " 更新: cd $NEXUS_ROOT && nx update"
echo " 巡检 cron: sudo nx cron"
echo " 手动备份: sudo bash $NEXUS_ROOT/deploy/db_backup.sh"
echo " 验收: bash $NEXUS_ROOT/deploy/verify-1panel-install-wizard.sh"
echo ""
return 0
fi
echo "=========================================="
echo " 安装向导后续步骤"
echo "=========================================="
echo "1. 1Panel 数据库 → 建库 nexus、用户 nexus(仅授权 nexus 库)"
echo "2. 浏览器打开: https://${domain}/app/install.html"
echo "3. 步骤 2MySQL/Redis TCP 应显示 ✓(容器名:端口)"
echo "4. 步骤 3:确认 db_host/redis_host 为 1Panel-mysql/redis-xxxx,填 MySQL 密码"
echo "5. 步骤 4:连接检测全绿 → 创建 admin"
echo "6. 步骤 5:锁定安装"
echo ""
if [[ "$FAILURES" -gt 0 ]]; then
echo "先修复上方 [FAIL],再执行:"
echo " cd $NEXUS_ROOT && nx update --no-cache"
fi
}
main() {
require_root
echo "Nexus 1Panel 安装向导验收 — $(date -Iseconds)"
echo "NEXUS_ROOT=$NEXUS_ROOT"
echo ""
check_repo || true
check_env_prod || true
resolve_nexus_container || true
check_1panel_network || true
probe_from_container || true
check_install_mode || true
check_http_local || true
check_install_api || true
check_ops_cron || true
check_mysql_backup || true
check_container_image_features || true
check_openresty_hint || true
print_next_steps
if [[ "$FAILURES" -gt 0 ]]; then
echo ""
fail "合计 $FAILURES 项未通过"
exit 1
fi
echo ""
if [[ "${INSTALL_WIZARD_ARCHIVED:-false}" == true ]]; then
pass "验收通过 — 安装已完成,请访问 /app/ 登录"
if [[ "$WARNS" -gt 0 ]]; then
warn "有 ${WARNS} 项 [WARN](cron/备份等建议项,不阻断登录)"
fi
else
pass "验收通过 — 可打开安装向导完成步骤 1–5"
fi
}
main "$@"