diff --git a/deploy/README-1panel.md b/deploy/README-1panel.md index d4050222..256fde78 100644 --- a/deploy/README-1panel.md +++ b/deploy/README-1panel.md @@ -164,6 +164,23 @@ MySQL 密码在 1Panel 自建库时设定,向导步骤 3 填写。 --- +## 安装向导验收(服务器执行) + +在 **1Panel 终端**(root)一键检查网络、容器名预填、本地 `/health` 与 `/api/install/env-check`: + +```bash +cd /opt/nexus && bash deploy/verify-1panel-install-wizard.sh +``` + +通过后再浏览器打开 `https://你的域名/app/install.html`。常见修复: + +```bash +cd /opt/nexus && bash deploy/detect-1panel-services.sh +nx update --no-cache +``` + +外网 HTTPS 仍失败但脚本本地检查全绿 → 在 1Panel **网站**配置反代 `http://127.0.0.1:8600`(见 `deploy/1panel/openresty-nexus.conf.example`)。 + ## 安装向导热修复 ```bash diff --git a/deploy/verify-1panel-install-wizard.sh b/deploy/verify-1panel-install-wizard.sh new file mode 100644 index 00000000..1b22d847 --- /dev/null +++ b/deploy/verify-1panel-install-wizard.sh @@ -0,0 +1,278 @@ +#!/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 + +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 +} + +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 +} + +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" + 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; then + fail "已锁定安装向导 — 应访问 /app/ 登录" + 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)" + 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() { + 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 "$@" diff --git a/docs/changelog/2026-06-06-1panel-install-wizard-verify-script.md b/docs/changelog/2026-06-06-1panel-install-wizard-verify-script.md new file mode 100644 index 00000000..4c7bdaa0 --- /dev/null +++ b/docs/changelog/2026-06-06-1panel-install-wizard-verify-script.md @@ -0,0 +1,28 @@ +# 2026-06-06 — 1Panel 安装向导服务器验收脚本 + +## 摘要 + +新增 `deploy/verify-1panel-install-wizard.sh`,在 VPS 上一键检查 1panel-network、容器名 sync、安装模式与 `/api/install/env-check` 预填,便于完成 `/app/install.html` 五步向导前的排查。 + +## 动机 + +`b25d079` 已修复 `nx update` 同步 `NEXUS_1PANEL_*_HOST`,但外网 HTTPS/OpenResty 与服务器内网状态需分步验证;Agent 无法直连用户 VPS 时,需提供可复制的服务器侧验收命令。 + +## 涉及文件 + +- `deploy/verify-1panel-install-wizard.sh` — 新建 +- `deploy/README-1panel.md` — 增加「安装向导验收」节 + +## 验证 + +在 1Panel 终端: + +```bash +cd /opt/nexus && bash deploy/verify-1panel-install-wizard.sh +``` + +期望:`[PASS]` 含 b25d079、1panel-network、步骤 3 `db_host=1Panel-mysql-*`;失败项按脚本提示执行 `nx update --no-cache`。 + +## 迁移 / 重启 + +无 DB 迁移。仅文档与脚本,不影响运行中容器。