2026-06-06 06:05:15 +08:00
|
|
|
|
#!/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} $*"; }
|
|
|
|
|
|
info() { echo -e " $*"; }
|
|
|
|
|
|
|
|
|
|
|
|
FAILURES=0
|
2026-06-06 21:53:03 +08:00
|
|
|
|
INSTALL_WIZARD_ARCHIVED=false
|
2026-06-06 06:05:15 +08:00
|
|
|
|
|
|
|
|
|
|
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 "已包含 b25d079(1Panel 容器名 sync 修复)"
|
|
|
|
|
|
else
|
|
|
|
|
|
warn "早于 b25d079 — 步骤 3 可能仍预填 host.docker.internal"
|
|
|
|
|
|
info "修复: cd $NEXUS_ROOT && nx update --no-cache"
|
|
|
|
|
|
fi
|
2026-06-06 07:51:52 +08:00
|
|
|
|
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
|
2026-06-06 06:05:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 存在"
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-06 07:51:52 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-06 06:05:15 +08:00
|
|
|
|
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"
|
2026-06-06 07:51:52 +08:00
|
|
|
|
check_app_env_redis_url
|
2026-06-06 06:05:15 +08:00
|
|
|
|
info "若需重来: 删卷 nexus-state 中 .env 并设 NEXUS_INSTALL_WIZARD_PENDING=1 后 nx update"
|
|
|
|
|
|
else
|
|
|
|
|
|
pass "/app/.env 不存在(安装模式 OK)"
|
|
|
|
|
|
fi
|
2026-06-06 21:53:03 +08:00
|
|
|
|
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
|
2026-06-06 06:05:15 +08:00
|
|
|
|
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_http_local() {
|
|
|
|
|
|
local health code
|
|
|
|
|
|
health="$(curl -sf http://127.0.0.1:8600/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:8600/app/install.html 2>/dev/null || echo 000)"
|
2026-06-06 21:53:03 +08:00
|
|
|
|
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
|
2026-06-06 06:05:15 +08:00
|
|
|
|
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() {
|
2026-06-06 21:53:43 +08:00
|
|
|
|
if [[ "${INSTALL_WIZARD_ARCHIVED:-false}" == true ]]; then
|
|
|
|
|
|
pass "安装 API 已关闭(向导已锁定,/api/install/* 返回 403 为预期)"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
2026-06-06 06:05:15 +08:00
|
|
|
|
local status env_json
|
|
|
|
|
|
status="$(curl -sf http://127.0.0.1:8600/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:8600/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_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() {
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo "=========================================="
|
|
|
|
|
|
echo " 安装向导后续步骤"
|
|
|
|
|
|
echo "=========================================="
|
|
|
|
|
|
echo "1. 1Panel 数据库 → 建库 nexus、用户 nexus(仅授权 nexus 库)"
|
|
|
|
|
|
echo "2. 浏览器打开: https://$(grep -E '^NEXUS_API_BASE_URL=' "$ENV_PROD" 2>/dev/null | sed 's|.*https\?://||;s|/.*||' || echo '你的域名')/app/install.html"
|
|
|
|
|
|
echo "3. 步骤 2:MySQL/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_openresty_hint || true
|
|
|
|
|
|
print_next_steps
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "$FAILURES" -gt 0 ]]; then
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
fail "合计 $FAILURES 项未通过"
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
pass "验收通过 — 可打开安装向导完成步骤 1–5"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
main "$@"
|