release: nexus btpanel session fix and app-v2
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# Nexus Docker
|
||||
|
||||
## 前置
|
||||
|
||||
- [Docker Desktop](https://www.docker.com/products/docker-desktop/)(Windows/macOS)或 Docker Engine + Compose v2
|
||||
|
||||
## 快速启动(正常模式)
|
||||
|
||||
```bash
|
||||
# 1. 生成密钥(写入 docker/.env,已 gitignore)
|
||||
python docker/generate_env.py
|
||||
|
||||
# 2. 构建并启动
|
||||
docker compose up -d --build
|
||||
|
||||
# 3. 健康检查
|
||||
curl -s http://localhost:8600/health # 期望: ok
|
||||
|
||||
# 4. 打开 SPA
|
||||
# http://localhost:8600/app/
|
||||
```
|
||||
|
||||
默认管理员需在安装向导或已有 DB 中配置;若 DB 为空且未跑过 install,可删卷后走安装模式。
|
||||
|
||||
## 安装向导模式
|
||||
|
||||
不创建 `docker/.env`(或清空 `NEXUS_SECRET_KEY`)时,应用以 **安装模式** 启动:
|
||||
|
||||
- http://localhost:8600/app/install.html
|
||||
|
||||
完成向导后 `.env` 会写入容器;`nexus-state` 卷会在重启时恢复配置。
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
docker compose logs -f nexus
|
||||
docker compose down # 停止,保留数据卷
|
||||
docker compose down -v # 停止并删除数据卷(清空 DB)
|
||||
docker compose build --no-cache nexus
|
||||
```
|
||||
|
||||
## 服务
|
||||
|
||||
| 环境 | Compose 服务 | 说明 |
|
||||
|------|-------------|------|
|
||||
| 本地开发 `docker-compose.yml` | nexus + mysql + redis | 一键本地联调 |
|
||||
| 生产 `docker-compose.prod.yml` | **仅 nexus** | MySQL/Redis 宿主机或 1Panel 自建 |
|
||||
|
||||
生产 1Panel:`docker/.env.prod` 可含 `NEXUS_REDIS_URL`(供 `nx update` 探测同步),**不经 Compose 注入容器**;运行时 Redis/MySQL URL 以安装向导写入的 `/app/.env`(`nexus-state` 卷)为准。MySQL `NEXUS_DATABASE_URL` 仅由向导步骤 3 写入。
|
||||
|
||||
## 生产注意
|
||||
|
||||
- 修改 `NEXUS_*` 为强密码;MySQL 使用自建库强密码。
|
||||
- 建议前置 Nginx/TLS,仅暴露 443。
|
||||
- 多实例部署需共享 MySQL、Redis;后台任务由 Redis primary lock 保证单实例执行。
|
||||
|
||||
### 1Panel 生产(Compose + OpenResty)
|
||||
|
||||
```bash
|
||||
cp docker/.env.prod.example docker/.env.prod # 从宝塔 .env 迁入密钥
|
||||
docker compose -f docker/docker-compose.prod.yml --env-file docker/.env.prod up -d --build
|
||||
```
|
||||
|
||||
- 镜像:`Dockerfile.prod`(含 `npm ci` + `vite build` + **`web/app/install.html` 安装向导**)
|
||||
- 仅绑定 `127.0.0.1:8600`;OpenResty 反代见 `deploy/1panel/openresty-nexus.conf.example`
|
||||
- **2C8G 调优**:`docker/profiles/2c8g.env`(`NEXUS_MEM_LIMIT` + `NEXUS_DB_POOL_SIZE=30`);外置 MySQL 可参考 `docker/mysql-prod-2c8g.cnf` 手工调优
|
||||
- 完整迁移步骤:`docs/design/plans/2026-06-04-1panel-docker-production.md`
|
||||
- 一键安装:`bash deploy/install-1panel-docker.sh`
|
||||
- 一键升级:`bash deploy/upgrade-1panel-docker.sh`(`--check` 仅查更新)
|
||||
|
||||
详见 `docs/design/specs/2026-06-03-docker-design.md`。
|
||||
@@ -0,0 +1,12 @@
|
||||
# 1Panel overlay — join Nexus to 1panel-network (App Store MySQL/Redis use this network).
|
||||
# Applied automatically when `docker network inspect 1panel-network` succeeds (deploy/nexus-1panel.sh).
|
||||
|
||||
services:
|
||||
nexus:
|
||||
networks:
|
||||
- 1panel-network
|
||||
|
||||
networks:
|
||||
1panel-network:
|
||||
external: true
|
||||
name: 1panel-network
|
||||
@@ -0,0 +1,61 @@
|
||||
# Nexus 6.0 — production Compose (1Panel / bare Docker host)
|
||||
# MySQL / Redis 不在 Compose 内 — 请在宿主机或 1Panel 自行安装
|
||||
#
|
||||
# Usage:
|
||||
# bash deploy/nexus-1panel.sh install --profile 2c8g
|
||||
# docker compose -f docker/docker-compose.prod.yml \
|
||||
# --env-file docker/.env.prod --env-file docker/profiles/2c8g.env up -d --build
|
||||
#
|
||||
# OpenResty on host proxies https://api.synaglobal.vip → 127.0.0.1:8600
|
||||
|
||||
name: nexus-prod
|
||||
|
||||
services:
|
||||
nexus:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Dockerfile.prod
|
||||
restart: unless-stopped
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
ports:
|
||||
- "127.0.0.1:${NEXUS_PUBLISH_PORT:-8600}:8600"
|
||||
environment:
|
||||
NEXUS_PERSIST_DIR: /var/lib/nexus
|
||||
NEXUS_DOCKER_WRITE_ENV: "1"
|
||||
NEXUS_HOST: "0.0.0.0"
|
||||
NEXUS_PORT: "8600"
|
||||
NEXUS_DEPLOY_PATH: /app
|
||||
NEXUS_HOST_ROOT: ${NEXUS_HOST_ROOT:-/opt/nexus}
|
||||
# Redis URL 仅来自 /app/.env(安装向导 + entrypoint export_app_env),勿在此注入以免覆盖密码
|
||||
NEXUS_1PANEL_DB_HOST: ${NEXUS_1PANEL_DB_HOST:-}
|
||||
NEXUS_1PANEL_REDIS_HOST: ${NEXUS_1PANEL_REDIS_HOST:-}
|
||||
NEXUS_CORS_ORIGINS: ${NEXUS_CORS_ORIGINS:?set NEXUS_CORS_ORIGINS}
|
||||
NEXUS_SECRET_KEY: ${NEXUS_SECRET_KEY:?set NEXUS_SECRET_KEY}
|
||||
NEXUS_API_KEY: ${NEXUS_API_KEY:?set NEXUS_API_KEY}
|
||||
NEXUS_ENCRYPTION_KEY: ${NEXUS_ENCRYPTION_KEY:?set NEXUS_ENCRYPTION_KEY}
|
||||
NEXUS_INSTALL_WIZARD_PENDING: ${NEXUS_INSTALL_WIZARD_PENDING:-0}
|
||||
NEXUS_API_BASE_URL: ${NEXUS_API_BASE_URL:?set NEXUS_API_BASE_URL}
|
||||
NEXUS_DB_POOL_SIZE: ${NEXUS_DB_POOL_SIZE:-30}
|
||||
NEXUS_DB_MAX_OVERFLOW: ${NEXUS_DB_MAX_OVERFLOW:-20}
|
||||
volumes:
|
||||
- nexus-state:/var/lib/nexus
|
||||
- nexus-web-data:/app/web/data
|
||||
mem_limit: ${NEXUS_MEM_LIMIT:-2g}
|
||||
cpus: ${NEXUS_CPUS:-1.5}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8600/health | grep -q '^ok' || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 90s
|
||||
networks:
|
||||
- nexus-internal
|
||||
|
||||
networks:
|
||||
nexus-internal:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
nexus-state:
|
||||
nexus-web-data:
|
||||
@@ -0,0 +1,175 @@
|
||||
#!/bin/sh
|
||||
# Nexus Docker entrypoint — wait for deps, sync .env with persistent volume, exec uvicorn.
|
||||
set -eu
|
||||
|
||||
PERSIST_DIR="${NEXUS_PERSIST_DIR:-/var/lib/nexus}"
|
||||
PERSIST_ENV="${PERSIST_DIR}/.env"
|
||||
|
||||
wait_tcp() {
|
||||
host="$1"
|
||||
port="$2"
|
||||
echo "entrypoint: waiting for ${host}:${port}..."
|
||||
until python3 -c "
|
||||
import socket
|
||||
s = socket.socket()
|
||||
s.settimeout(2)
|
||||
s.connect(('${host}', ${port}))
|
||||
s.close()
|
||||
" 2>/dev/null; do
|
||||
sleep 2
|
||||
done
|
||||
}
|
||||
|
||||
wait_tcp_optional() {
|
||||
host="$1"
|
||||
port="$2"
|
||||
tries="${3:-15}"
|
||||
label="${4:-service}"
|
||||
echo "entrypoint: waiting for ${label} ${host}:${port} (max ${tries} tries)..."
|
||||
n=0
|
||||
while [ "$n" -lt "$tries" ]; do
|
||||
if python3 -c "
|
||||
import socket
|
||||
s = socket.socket()
|
||||
s.settimeout(2)
|
||||
s.connect(('${host}', ${port}))
|
||||
s.close()
|
||||
" 2>/dev/null; then
|
||||
echo "entrypoint: ${label} ready at ${host}:${port}"
|
||||
return 0
|
||||
fi
|
||||
n=$((n + 1))
|
||||
sleep 2
|
||||
done
|
||||
echo "entrypoint: warn — ${label} not reachable at ${host}:${port}; install on host or set URL in .env"
|
||||
return 0
|
||||
}
|
||||
|
||||
restore_persisted_install_lock() {
|
||||
if [ "${NEXUS_INSTALL_WIZARD_PENDING:-0}" = "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
PERSIST_LOCK="${PERSIST_DIR}/.install_locked"
|
||||
if [ -f "${PERSIST_LOCK}" ] && [ ! -f /app/.install_locked ]; then
|
||||
cp "${PERSIST_LOCK}" /app/.install_locked
|
||||
chmod 600 /app/.install_locked
|
||||
echo "entrypoint: restored .install_locked from ${PERSIST_LOCK}"
|
||||
fi
|
||||
}
|
||||
|
||||
archive_install_wizard_if_locked() {
|
||||
if [ "${NEXUS_INSTALL_WIZARD_PENDING:-0}" = "1" ]; then
|
||||
rm -f /app/.install_locked "${PERSIST_DIR}/.install_locked" 2>/dev/null || true
|
||||
if [ -f /app/web/app/install.html.bak ] && [ ! -f /app/web/app/install.html ]; then
|
||||
mv /app/web/app/install.html.bak /app/web/app/install.html
|
||||
echo "entrypoint: restored install.html from .bak (wizard pending)"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
if [ ! -f /app/.install_locked ] && [ ! -f "${PERSIST_DIR}/.install_locked" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ -f /app/web/app/install.html.bak ]; then
|
||||
if [ -f /app/web/app/install.html ]; then
|
||||
rm -f /app/web/app/install.html
|
||||
echo "entrypoint: removed install.html (wizard archived as install.html.bak)"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
if [ -f /app/web/app/install.html ]; then
|
||||
mv /app/web/app/install.html /app/web/app/install.html.bak
|
||||
echo "entrypoint: archived install.html → install.html.bak"
|
||||
fi
|
||||
}
|
||||
|
||||
restore_persisted_env() {
|
||||
if [ "${NEXUS_INSTALL_WIZARD_PENDING:-0}" = "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ ! -f "${PERSIST_ENV}" ]; then
|
||||
return 0
|
||||
fi
|
||||
if ! grep -q '^NEXUS_DATABASE_URL=' "${PERSIST_ENV}" 2>/dev/null; then
|
||||
echo "entrypoint: warn — ${PERSIST_ENV} missing NEXUS_DATABASE_URL; skip restore (complete install wizard)"
|
||||
return 0
|
||||
fi
|
||||
cp "${PERSIST_ENV}" /app/.env
|
||||
chmod 600 /app/.env
|
||||
echo "entrypoint: restored .env from ${PERSIST_ENV}"
|
||||
}
|
||||
|
||||
# Compose may inject placeholder NEXUS_REDIS_URL (no password). Wizard .env wins.
|
||||
export_app_env() {
|
||||
if [ ! -f /app/.env ]; then
|
||||
return 0
|
||||
fi
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
. /app/.env
|
||||
set +a
|
||||
echo "entrypoint: exported NEXUS_* from /app/.env (overrides Compose placeholders)"
|
||||
}
|
||||
|
||||
write_env_from_environment() {
|
||||
if [ -f /app/.env ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "${NEXUS_DOCKER_WRITE_ENV:-}" != "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ -z "${NEXUS_SECRET_KEY:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "${NEXUS_INSTALL_WIZARD_PENDING:-}" = "1" ]; then
|
||||
echo "entrypoint: install wizard pending — skip writing /app/.env"
|
||||
return 0
|
||||
fi
|
||||
if [ -z "${NEXUS_DATABASE_URL:-}" ]; then
|
||||
echo "entrypoint: skip writing /app/.env — NEXUS_DATABASE_URL not set (use /app/install.html)"
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "${PERSIST_DIR}"
|
||||
{
|
||||
echo "# Generated by docker/entrypoint.sh — do not commit"
|
||||
env | grep '^NEXUS_' | sort
|
||||
} > /app/.env
|
||||
chmod 600 /app/.env
|
||||
cp /app/.env "${PERSIST_ENV}"
|
||||
echo "entrypoint: wrote .env from container environment"
|
||||
}
|
||||
|
||||
persist_state_on_exit() {
|
||||
if [ -f /app/.env ]; then
|
||||
mkdir -p "${PERSIST_DIR}"
|
||||
cp /app/.env "${PERSIST_ENV}"
|
||||
chmod 600 "${PERSIST_ENV}" 2>/dev/null || true
|
||||
fi
|
||||
if [ -f /app/.install_locked ]; then
|
||||
mkdir -p "${PERSIST_DIR}"
|
||||
cp /app/.install_locked "${PERSIST_DIR}/.install_locked"
|
||||
chmod 600 "${PERSIST_DIR}/.install_locked" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
trap persist_state_on_exit EXIT INT TERM
|
||||
|
||||
# MySQL / Redis 均由用户自行安装在宿主机;安装向导阶段不阻塞启动
|
||||
if [ "${NEXUS_INSTALL_WIZARD_PENDING:-0}" != "1" ] && [ "${NEXUS_SKIP_MYSQL_WAIT:-0}" != "1" ]; then
|
||||
MYSQL_HOST="${NEXUS_1PANEL_DB_HOST:-${MYSQL_HOST:-host.docker.internal}}"
|
||||
MYSQL_PORT="${MYSQL_PORT:-3306}"
|
||||
wait_tcp_optional "${MYSQL_HOST}" "${MYSQL_PORT}" 30 "MySQL"
|
||||
fi
|
||||
|
||||
if [ "${NEXUS_INSTALL_WIZARD_PENDING:-0}" != "1" ] && [ "${NEXUS_SKIP_REDIS_WAIT:-0}" != "1" ]; then
|
||||
REDIS_HOST="${NEXUS_1PANEL_REDIS_HOST:-${REDIS_HOST:-host.docker.internal}}"
|
||||
REDIS_PORT="${REDIS_PORT:-6379}"
|
||||
wait_tcp_optional "${REDIS_HOST}" "${REDIS_PORT}" 15 "Redis"
|
||||
fi
|
||||
|
||||
restore_persisted_install_lock
|
||||
archive_install_wizard_if_locked
|
||||
restore_persisted_env
|
||||
write_env_from_environment
|
||||
export_app_env
|
||||
|
||||
exec "$@"
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate docker/.env with random secrets for local Compose."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from scripts.generate_nexus_secrets import ( # noqa: E402
|
||||
generate_nexus_secrets,
|
||||
write_env_prod,
|
||||
)
|
||||
|
||||
OUT = ROOT / "docker" / ".env"
|
||||
EXAMPLE = ROOT / "docker" / ".env.example"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if OUT.exists():
|
||||
raise SystemExit(f"Refusing to overwrite existing {OUT} — delete it first.")
|
||||
|
||||
secrets_map = generate_nexus_secrets()
|
||||
write_env_prod(
|
||||
EXAMPLE,
|
||||
OUT,
|
||||
domain="localhost",
|
||||
secrets_map=secrets_map,
|
||||
profile_env=None,
|
||||
wizard_pending=False,
|
||||
)
|
||||
print(f"Wrote {OUT}")
|
||||
print("Next: docker compose up -d --build")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,20 @@
|
||||
# MySQL 8.4 — Nexus production tuning for 1 vCPU / 4 GiB host (minimal)
|
||||
|
||||
[mysqld]
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
|
||||
innodb_buffer_pool_size = 512M
|
||||
innodb_log_file_size = 128M
|
||||
|
||||
max_connections = 80
|
||||
|
||||
innodb_read_io_threads = 2
|
||||
innodb_write_io_threads = 2
|
||||
|
||||
table_open_cache = 1000
|
||||
tmp_table_size = 32M
|
||||
max_heap_table_size = 32M
|
||||
|
||||
[client]
|
||||
default-character-set = utf8mb4
|
||||
@@ -0,0 +1,25 @@
|
||||
# MySQL 8.4 — Nexus production tuning for 2 vCPU / 8 GiB host
|
||||
# Mounted by docker/docker-compose.prod.yml (see docs/design/plans/2026-06-04-1panel-docker-production.md)
|
||||
|
||||
[mysqld]
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
|
||||
# ~1 GiB for InnoDB (leave RAM for Nexus, Redis, 1Panel, OS)
|
||||
innodb_buffer_pool_size = 1024M
|
||||
innodb_log_file_size = 256M
|
||||
|
||||
# Nexus async pool default 30+20; leave headroom for admin/migrations
|
||||
max_connections = 120
|
||||
|
||||
# 2 vCPU: avoid aggressive IO threads
|
||||
innodb_read_io_threads = 4
|
||||
innodb_write_io_threads = 4
|
||||
|
||||
# Sensible defaults on small VMs
|
||||
table_open_cache = 2000
|
||||
tmp_table_size = 64M
|
||||
max_heap_table_size = 64M
|
||||
|
||||
[client]
|
||||
default-character-set = utf8mb4
|
||||
@@ -0,0 +1,20 @@
|
||||
# MySQL 8.4 — Nexus production tuning for 4 vCPU / 16 GiB host
|
||||
|
||||
[mysqld]
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
|
||||
innodb_buffer_pool_size = 4096M
|
||||
innodb_log_file_size = 512M
|
||||
|
||||
max_connections = 200
|
||||
|
||||
innodb_read_io_threads = 8
|
||||
innodb_write_io_threads = 8
|
||||
|
||||
table_open_cache = 4000
|
||||
tmp_table_size = 128M
|
||||
max_heap_table_size = 128M
|
||||
|
||||
[client]
|
||||
default-character-set = utf8mb4
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Write project-root .env from docker/.env for host uvicorn (127.0.0.1 MySQL/Redis)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from server.utils.text_io import read_utf8_lf, write_utf8_lf
|
||||
DOCKER_ENV = ROOT / "docker" / ".env"
|
||||
OUT = ROOT / ".env"
|
||||
|
||||
|
||||
def _parse(path: Path) -> dict[str, str]:
|
||||
data: dict[str, str] = {}
|
||||
for line in read_utf8_lf(path).splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, _, value = line.partition("=")
|
||||
data[key.strip()] = value.strip()
|
||||
return data
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not DOCKER_ENV.exists():
|
||||
raise SystemExit(f"Missing {DOCKER_ENV} — run: python3 docker/generate_env.py")
|
||||
|
||||
d = _parse(DOCKER_ENV)
|
||||
mysql_pass = d.get("MYSQL_PASSWORD", "")
|
||||
if not mysql_pass:
|
||||
raise SystemExit("MYSQL_PASSWORD missing in docker/.env")
|
||||
|
||||
for key in ("NEXUS_SECRET_KEY", "NEXUS_API_KEY", "NEXUS_ENCRYPTION_KEY"):
|
||||
if not d.get(key):
|
||||
raise SystemExit(f"{key} missing in docker/.env — run generate_env.py")
|
||||
|
||||
db_user = "nexus"
|
||||
db_pass_q = quote_plus(mysql_pass)
|
||||
deploy = str(ROOT)
|
||||
cors = d.get("NEXUS_CORS_ORIGINS", "http://localhost:8600,http://127.0.0.1:8600")
|
||||
api_base = d.get("NEXUS_API_BASE_URL", "http://localhost:8600")
|
||||
|
||||
lines = [
|
||||
"# Nexus .env — host uvicorn dev (from docker/sync_root_env.py)",
|
||||
"",
|
||||
"NEXUS_HOST=0.0.0.0",
|
||||
"NEXUS_PORT=8600",
|
||||
f"NEXUS_DEPLOY_PATH={deploy}",
|
||||
f"NEXUS_CORS_ORIGINS={cors}",
|
||||
f"NEXUS_API_BASE_URL={api_base}",
|
||||
"",
|
||||
f"NEXUS_DATABASE_URL=mysql+aiomysql://{db_user}:{db_pass_q}@127.0.0.1:3306/nexus",
|
||||
"NEXUS_DB_POOL_SIZE=20",
|
||||
"NEXUS_DB_MAX_OVERFLOW=10",
|
||||
"",
|
||||
f"NEXUS_SECRET_KEY={d['NEXUS_SECRET_KEY']}",
|
||||
f"NEXUS_API_KEY={d['NEXUS_API_KEY']}",
|
||||
f"NEXUS_ENCRYPTION_KEY={d['NEXUS_ENCRYPTION_KEY']}",
|
||||
"",
|
||||
"NEXUS_REDIS_URL=redis://127.0.0.1:6379/0",
|
||||
"NEXUS_SSH_STRICT_HOST_CHECKING=false",
|
||||
"",
|
||||
"# MySQL MCP (optional): python3 scripts/sync_mysql_mcp_env.py",
|
||||
"MYSQL_HOST=127.0.0.1",
|
||||
"MYSQL_PORT=3306",
|
||||
f"MYSQL_USER={db_user}",
|
||||
f"MYSQL_PASSWORD={mysql_pass}",
|
||||
"MYSQL_DATABASE=nexus",
|
||||
"MYSQL_READONLY=false",
|
||||
"MYSQL_DATABASE_ALLOWLIST=nexus",
|
||||
]
|
||||
|
||||
write_utf8_lf(OUT, "\n".join(lines) + "\n")
|
||||
OUT.chmod(0o600)
|
||||
print(f"Wrote {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user