diff --git a/deploy/README-1panel.md b/deploy/README-1panel.md index 256fde78..754f1d02 100644 --- a/deploy/README-1panel.md +++ b/deploy/README-1panel.md @@ -9,6 +9,7 @@ | `install-nexus-fresh.sh` | 全新空库安装 | `bash deploy/install-nexus-fresh.sh --skip-clone` | | **`update.sh`** | **一键更新** | `curl -fsSL ".../update.sh" \| bash` | | `nexus-1panel.sh` | 底层 install / upgrade | `bash deploy/nexus-1panel.sh upgrade` | +| `fix-1panel-mysql-grant.sh` | 修复 1045 nexus@Docker | `bash deploy/fix-1panel-mysql-grant.sh` | | **`nx`** | **统一运维菜单** | `nx` | 档位:`--profile 1c4g` · `2c8g`(默认)· `4c16g` @@ -120,6 +121,26 @@ Docker 栈**仅含 Nexus 容器**。MySQL / Redis 在 **1Panel 应用商店**安 3. `nx update` 后打开安装向导步骤 3(主机应已预填 `1Panel-mysql-…`) 4. 填写 MySQL 密码;Redis 若设了密码一并填写 +### 常见错误 1045(Access denied for 'nexus'@'172.18.x.x') + +TCP 已通,但 MySQL 用户多为 **`nexus@localhost`**,Docker 内 Nexus 从 **172.x** 连接会被拒绝。 + +在服务器执行(按提示输入 root 密码与 nexus 密码): + +```bash +cd /opt/nexus && bash deploy/fix-1panel-mysql-grant.sh +``` + +或一次性: + +```bash +MYSQL_ROOT_PASSWORD='1Panel里MySQL的root密码' \ +NEXUS_DB_PASSWORD='向导里要填的nexus密码' \ +bash /opt/nexus/deploy/fix-1panel-mysql-grant.sh +``` + +然后安装向导步骤 3:**库名/用户 `nexus`**,主机为 `1Panel-mysql-xxxx`,密码与上面一致。 + 若仍报 `Can't connect to MySQL/Redis on 'host.docker.internal'`:说明 Nexus **未接入 1panel-network** 或未探测到容器名,在服务器执行: ```bash diff --git a/deploy/fix-1panel-mysql-grant.sh b/deploy/fix-1panel-mysql-grant.sh new file mode 100644 index 00000000..bad94612 --- /dev/null +++ b/deploy/fix-1panel-mysql-grant.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# Grant nexus@'%' on 1Panel App Store MySQL for Docker (1panel-network) install wizard. +# Fixes: Access denied for user 'nexus'@'172.18.x.x' (1045) +# +# Usage: +# MYSQL_ROOT_PASSWORD='root密码' NEXUS_DB_PASSWORD='nexus密码' bash deploy/fix-1panel-mysql-grant.sh +# bash deploy/fix-1panel-mysql-grant.sh # prompts +set -euo pipefail + +NEXUS_DB_NAME="${NEXUS_DB_NAME:-nexus}" +NEXUS_DB_USER="${NEXUS_DB_USER:-nexus}" + +info() { echo -e "\033[0;32m[INFO]\033[0m $*"; } +warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; } +error() { echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; exit 1; } + +pick_mysql_container() { + local name + if [[ -n "${MYSQL_CONTAINER:-}" ]]; then + echo "$MYSQL_CONTAINER" + return 0 + fi + if docker network inspect 1panel-network >/dev/null 2>&1; then + name="$(docker network inspect 1panel-network --format '{{range .Containers}}{{.Name}} {{end}}' \ + | tr ' ' '\n' | grep -iE '^1Panel-mysql-' | head -1 || true)" + [[ -n "$name" ]] && echo "$name" && return 0 + fi + name="$(docker ps --format '{{.Names}}' | grep -iE '^1Panel-mysql-' | head -1 || true)" + [[ -n "$name" ]] && echo "$name" && return 0 + error "未找到 1Panel MySQL 容器(可设 MYSQL_CONTAINER=名称)" +} + +prompt_secret() { + local var="$1" prompt="$2" + if [[ -n "${!var:-}" ]]; then + return 0 + fi + read -r -s -p "$prompt: " "$var" + echo "" + if [[ -z "${!var:-}" ]]; then + error "$var 不能为空" + fi +} + +main() { + [[ "${EUID:-$(id -u)}" -eq 0 ]] || error "请用 root 运行" + + local mysql_c + mysql_c="$(pick_mysql_container)" + info "MySQL 容器: $mysql_c" + + prompt_secret MYSQL_ROOT_PASSWORD "1Panel MySQL root 密码" + prompt_secret NEXUS_DB_PASSWORD "nexus 用户密码(与安装向导步骤 3 一致)" + + info "创建库/用户并授权 ${NEXUS_DB_USER}@'%' → ${NEXUS_DB_NAME}.* ..." + + docker exec -i "$mysql_c" mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" < bool: return False +def _mysql_auth_error_detail(db_user: str) -> str: + """1045 Access denied — common on 1Panel when user is only nexus@localhost.""" + if os.environ.get("NEXUS_DOCKER_WRITE_ENV") != "1": + return ( + f"MySQL 拒绝用户 {db_user!r} 的密码或主机权限。" + "请核对库名/用户名/密码,并确认该用户允许从应用主机连接(非仅 localhost)。" + ) + return ( + f"MySQL 拒绝用户 {db_user!r}(错误 1045)。Nexus 在 Docker 内通过 1panel-network 连接," + "来源 IP 为容器网段(如 172.18.x.x),不是 localhost。\n" + "请在 1Panel MySQL 中确保:① 库 nexus、用户 nexus 已创建 ② 密码与向导一致 " + "③ 存在 nexus@'%' 或允许 Docker 网段(仅 nexus@localhost 会失败)。\n" + "服务器执行: cd /opt/nexus && bash deploy/fix-1panel-mysql-grant.sh" + ) + + def _mysql_connect_error_detail(host: str, port: int) -> str: if os.environ.get("NEXUS_DOCKER_WRITE_ENV") != "1": return "" @@ -265,6 +281,11 @@ async def _test_mysql_url(db_url: str) -> tuple[bool, str]: await engine.dispose() return True, "✓ MySQL 连接正常" except Exception as e: + err = str(e) + if "1045" in err or "Access denied" in err: + return False, _mysql_auth_error_detail( + urlparse(db_url).username or "nexus" + ) return False, f"✗ MySQL 连接失败: {e}" @@ -805,6 +826,8 @@ async def init_db(req: InitDbRequest): except Exception as e: await engine.dispose() err = str(e) + if "1045" in err or "Access denied" in err: + raise HTTPException(400, _mysql_auth_error_detail(req.db_user)) from e if "2003" in err or "Can't connect to MySQL" in err: extra = _mysql_connect_error_detail(req.db_host, db_port) if extra: