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:
Nexus Deploy
2026-06-06 05:47:43 +08:00
parent 6b1d23c7d7
commit b25d0798f6
6 changed files with 137 additions and 11 deletions
+1
View File
@@ -69,6 +69,7 @@ web/uploads/
# Data (sensitive)
web/data/config.php
web/data/*.db
web/data/1panel-hosts.json
# Backups
backups/
+22 -6
View File
@@ -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
View File
@@ -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_hostNEXUS_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
View File
@@ -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
View File
@@ -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) {