fix(1panel): sync MySQL/Redis container hosts on nx update
Upgrade was skipping 1panel-network detection; wizard now overrides stale host.docker.internal from volume/API defaults. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -69,6 +69,7 @@ web/uploads/
|
||||
# Data (sensitive)
|
||||
web/data/config.php
|
||||
web/data/*.db
|
||||
web/data/1panel-hosts.json
|
||||
|
||||
# Backups
|
||||
backups/
|
||||
|
||||
@@ -5,9 +5,25 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
detect_1panel_container() {
|
||||
local prefix="$1"
|
||||
docker ps --format '{{.Names}}' 2>/dev/null | grep -E "^${prefix}" | head -1 || true
|
||||
network_container_names() {
|
||||
docker network inspect 1panel-network --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null \
|
||||
| tr ' ' '\n' | sed '/^$/d'
|
||||
}
|
||||
|
||||
pick_service_container() {
|
||||
local kind="$1"
|
||||
local names line
|
||||
names="$(network_container_names)"
|
||||
if [[ -z "$names" ]]; then
|
||||
docker ps --format '{{.Names}}' 2>/dev/null | grep -iE "(^1Panel-${kind}-|${kind})" | head -1 || true
|
||||
return
|
||||
fi
|
||||
line="$(echo "$names" | grep -iE "^1Panel-${kind}-" | head -1 || true)"
|
||||
if [[ -n "$line" ]]; then
|
||||
echo "$line"
|
||||
return
|
||||
fi
|
||||
echo "$names" | grep -i "$kind" | head -1 || true
|
||||
}
|
||||
|
||||
if ! docker network inspect 1panel-network >/dev/null 2>&1; then
|
||||
@@ -15,11 +31,11 @@ if ! docker network inspect 1panel-network >/dev/null 2>&1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MYSQL_HOST="$(detect_1panel_container '1Panel-mysql-')"
|
||||
REDIS_HOST="$(detect_1panel_container '1Panel-redis-')"
|
||||
MYSQL_HOST="$(pick_service_container mysql)"
|
||||
REDIS_HOST="$(pick_service_container redis)"
|
||||
|
||||
if [[ -z "$MYSQL_HOST" && -z "$REDIS_HOST" ]]; then
|
||||
echo "No 1Panel MySQL/Redis containers running" >&2
|
||||
echo "No MySQL/Redis containers on 1panel-network" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
|
||||
+35
-2
@@ -200,12 +200,39 @@ upsert_env_var() {
|
||||
fi
|
||||
}
|
||||
|
||||
write_1panel_hosts_volume() {
|
||||
local mysql_host="${1:-}" redis_host="${2:-}"
|
||||
[[ -n "$mysql_host" || -n "$redis_host" ]] || return 0
|
||||
local vol json
|
||||
vol="$(docker volume ls -q --filter name=nexus-web-data 2>/dev/null | head -1 || true)"
|
||||
if [[ -z "$vol" ]]; then
|
||||
warn "未找到 nexus-web-data 卷,跳过写入 1panel-hosts.json"
|
||||
return 0
|
||||
fi
|
||||
json="{"
|
||||
if [[ -n "$mysql_host" ]]; then
|
||||
json+="\"db_host\":\"${mysql_host}\""
|
||||
[[ -n "$redis_host" ]] && json+=","
|
||||
fi
|
||||
if [[ -n "$redis_host" ]]; then
|
||||
json+="\"redis_host\":\"${redis_host}\""
|
||||
fi
|
||||
json+="}"
|
||||
if printf '%s' "$json" | docker run --rm -i -v "${vol}:/data" alpine:3.19 \
|
||||
sh -c 'cat > /data/1panel-hosts.json && chmod 644 /data/1panel-hosts.json'; then
|
||||
info "已写入卷 ${vol} → web/data/1panel-hosts.json"
|
||||
else
|
||||
warn "写入 1panel-hosts.json 失败(向导仍可读 .env.prod 环境变量)"
|
||||
fi
|
||||
}
|
||||
|
||||
sync_1panel_service_hosts() {
|
||||
local root="$1"
|
||||
local env_file="$root/$ENV_PROD"
|
||||
local detect="$root/deploy/detect-1panel-services.sh"
|
||||
[[ -f "$env_file" ]] || return 0
|
||||
if ! has_1panel_network; then
|
||||
warn "未找到 1panel-network — 请先在 1Panel 应用商店安装 MySQL/Redis"
|
||||
return 0
|
||||
fi
|
||||
if [[ ! -x "$detect" ]]; then
|
||||
@@ -215,7 +242,11 @@ sync_1panel_service_hosts() {
|
||||
warn "未找到 detect-1panel-services.sh,跳过 1Panel 容器名探测"
|
||||
return 0
|
||||
fi
|
||||
local line mysql_host redis_host
|
||||
local line mysql_host="" redis_host="" detect_out
|
||||
if ! detect_out="$("$detect" 2>&1)"; then
|
||||
warn "1Panel 容器探测: ${detect_out:-无 MySQL/Redis 容器运行}"
|
||||
return 0
|
||||
fi
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
NEXUS_1PANEL_DB_HOST=*)
|
||||
@@ -230,7 +261,8 @@ sync_1panel_service_hosts() {
|
||||
info "1Panel Redis 容器: $redis_host(NEXUS_REDIS_URL 已同步)"
|
||||
;;
|
||||
esac
|
||||
done < <("$detect" 2>/dev/null || true)
|
||||
done <<<"$detect_out"
|
||||
write_1panel_hosts_volume "$mysql_host" "$redis_host"
|
||||
}
|
||||
|
||||
compose_cmd() {
|
||||
@@ -852,6 +884,7 @@ cmd_upgrade() {
|
||||
apply_pool_to_env_prod "$NEXUS_ROOT" "$(profile_env_file "$NEXUS_ROOT")"
|
||||
save_host_profile "$NEXUS_ROOT"
|
||||
purge_legacy_bundled_services "$NEXUS_ROOT"
|
||||
sync_1panel_service_hosts "$NEXUS_ROOT"
|
||||
if [[ "$NO_CACHE" == true ]]; then
|
||||
step "无缓存重建 Nexus 镜像..."
|
||||
compose_cmd "$NEXUS_ROOT" build --no-cache nexus
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# 2026-06-05 — 修复 nx update 未同步 1Panel 容器名
|
||||
|
||||
## 摘要
|
||||
|
||||
用户步骤 3 仍填 `host.docker.internal`:`cmd_upgrade` 未调用 `sync_1panel_service_hosts`,容器环境变量与向导预填未更新。升级路径补 sync;探测改读 `1panel-network`;写入 `web/data/1panel-hosts.json` 作后备;向导加载时覆盖陈旧主机名。
|
||||
|
||||
## 根因
|
||||
|
||||
- `compose_up`(安装)会 sync,`cmd_upgrade`(日常 `nx update`)之前不会
|
||||
- 安装状态/表单可能缓存 `host.docker.internal`,覆盖 `docker_defaults`
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `deploy/nexus-1panel.sh` — upgrade 调 sync + 写卷
|
||||
- `deploy/detect-1panel-services.sh` — 从 1panel-network 枚举容器
|
||||
- `server/api/install.py` — 读 `1panel-hosts.json` 后备
|
||||
- `web/app/install.html` — `refreshDockerDefaults` 覆盖陈旧主机
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
nx update --no-cache
|
||||
grep 1PANEL docker/.env.prod
|
||||
docker exec nexus-prod-nexus-1 cat /app/web/data/1panel-hosts.json
|
||||
# 安装向导步骤 3 db_host 应为 1Panel-mysql-xxxx
|
||||
```
|
||||
+20
-2
@@ -33,6 +33,7 @@ ENV_FILE = ROOT_DIR / ".env"
|
||||
INSTALL_LOCK = ROOT_DIR / ".install_locked"
|
||||
CONFIG_DIR = ROOT_DIR / "web" / "data"
|
||||
CONFIG_JSON = CONFIG_DIR / "config.json"
|
||||
ONEPANEL_HOSTS_JSON = CONFIG_DIR / "1panel-hosts.json"
|
||||
STATE_FILE = ROOT_DIR / ".install_state.json"
|
||||
|
||||
|
||||
@@ -98,10 +99,27 @@ def _build_redis_url(req: InitDbRequest) -> str:
|
||||
return url
|
||||
|
||||
|
||||
def _read_1panel_hosts_file() -> dict[str, str]:
|
||||
if not ONEPANEL_HOSTS_JSON.is_file():
|
||||
return {}
|
||||
import json
|
||||
|
||||
try:
|
||||
raw = json.loads(read_utf8_text(ONEPANEL_HOSTS_JSON))
|
||||
except (json.JSONDecodeError, OSError):
|
||||
return {}
|
||||
if not isinstance(raw, dict):
|
||||
return {}
|
||||
return {str(k): str(v) for k, v in raw.items() if v}
|
||||
|
||||
|
||||
def _onepanel_service_host(kind: str) -> str | None:
|
||||
"""1Panel App Store container name (NEXUS_1PANEL_DB_HOST / REDIS_HOST)."""
|
||||
"""1Panel App Store container name (env → web/data/1panel-hosts.json)."""
|
||||
val = os.environ.get(f"NEXUS_1PANEL_{kind}_HOST", "").strip()
|
||||
return val or None
|
||||
if val:
|
||||
return val
|
||||
file_key = "db_host" if kind == "DB" else "redis_host"
|
||||
return _read_1panel_hosts_file().get(file_key) or None
|
||||
|
||||
|
||||
def _resolve_redis_probe() -> tuple[str, int, str | None]:
|
||||
|
||||
+33
-1
@@ -628,6 +628,35 @@ function installWizard() {
|
||||
return results.every(r => !r.includes('✗') && !r.includes('⚠'));
|
||||
},
|
||||
|
||||
isStaleDockerHost(host) {
|
||||
const h = (host || '').trim().toLowerCase();
|
||||
return !h || h === 'localhost' || h === '127.0.0.1' || h === 'host.docker.internal';
|
||||
},
|
||||
|
||||
applyDockerDefaults(defaults) {
|
||||
if (!defaults) return;
|
||||
if (this.isStaleDockerHost(this.form.db_host) && defaults.db_host) {
|
||||
this.form.db_host = defaults.db_host;
|
||||
}
|
||||
if (this.isStaleDockerHost(this.form.redis_host) && defaults.redis_host) {
|
||||
this.form.redis_host = defaults.redis_host;
|
||||
}
|
||||
if (!this.form.db_name || this.form.db_name === 'Nexus') this.form.db_name = defaults.db_name || this.form.db_name;
|
||||
if (!this.form.db_user || this.form.db_user === 'Nexus') this.form.db_user = defaults.db_user || this.form.db_user;
|
||||
if (defaults.db_port) this.form.db_port = defaults.db_port;
|
||||
if (defaults.redis_port) this.form.redis_port = defaults.redis_port;
|
||||
if (defaults.redis_db) this.form.redis_db = defaults.redis_db;
|
||||
},
|
||||
|
||||
async refreshDockerDefaults() {
|
||||
try {
|
||||
const r = await fetch(API + '/api/install/env-check');
|
||||
if (!r.ok) return;
|
||||
const d = await r.json();
|
||||
this.applyDockerDefaults(d.docker_defaults);
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
async init() {
|
||||
// Auto-detect site URL
|
||||
const proto = location.protocol === 'https:' ? 'https' : 'http';
|
||||
@@ -658,6 +687,9 @@ function installWizard() {
|
||||
if (d.step >= 4) this.checkConnections();
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
// 1Panel: override stale host.docker.internal with container names from API/volume
|
||||
await this.refreshDockerDefaults();
|
||||
},
|
||||
|
||||
async checkConnections() {
|
||||
@@ -690,7 +722,7 @@ function installWizard() {
|
||||
this.envChecks = d.checks;
|
||||
this.envAllPass = d.all_pass;
|
||||
if (d.docker_defaults) {
|
||||
Object.assign(this.form, d.docker_defaults);
|
||||
this.applyDockerDefaults(d.docker_defaults);
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
|
||||
Reference in New Issue
Block a user