diff --git a/.env.example b/.env.example index 582ab6c4..a32e27f5 100644 --- a/.env.example +++ b/.env.example @@ -53,7 +53,8 @@ # ---- 开发用 PostgreSQL(./scripts/dev.sh --postgres)---- # 这几项只有 dev.sh 读:写成环境变量或写在这个 .env 里都行(环境变量优先)。数据在 docker 数据卷里, -# 密码第一次自动生成在 data/.postgres-dev/,dev.sh 照它设 AGENTLAB_DATABASE_URL(盖过上面写的) +# 密码第一次自动生成在数据目录(和后端一样照 AGENTLAB_DATA_DIR,缺省 data/)的 .postgres-dev/ 里, +# dev.sh 照它设 AGENTLAB_DATABASE_URL(盖过上面写的) # AGENTLAB_DEV_PG_CONTAINER=agentlab-dev-postgres # AGENTLAB_DEV_PG_PORT=55432 # AGENTLAB_DEV_PG_VOLUME=agentlab-dev-pgdata diff --git a/backend/tests/test_deploy_script.py b/backend/tests/test_deploy_script.py index 77dcc803..022f33a3 100644 --- a/backend/tests/test_deploy_script.py +++ b/backend/tests/test_deploy_script.py @@ -813,7 +813,7 @@ def test_env_example_lists_every_database_pool_and_multiprocess_setting(): names = {f"AGENTLAB_{name.upper()}" for name in Settings.model_fields if name.startswith(prefixes)} assert {"AGENTLAB_DATABASE_URL", "AGENTLAB_DATABASE_DIRECT_URL", "AGENTLAB_DB_PGBOUNCER", "AGENTLAB_WORKERS", "AGENTLAB_WORKER_LEASE_S", "AGENTLAB_DB_STARTUP_TIMEOUT", "AGENTLAB_SCHEDULER_TICK_S"} <= names - dev = set(re.findall(r"dev_pg_setting (AGENTLAB_DEV_PG_[A-Z_]+)", (REPO / "scripts" / "dev.sh").read_text())) + dev = set(re.findall(r"dotenv_setting (AGENTLAB_DEV_PG_[A-Z_]+)", (REPO / "scripts" / "dev.sh").read_text())) assert len(dev) == 4 example = (REPO / ".env.example").read_text(encoding="utf-8") listed = set(re.findall(r"^# (AGENTLAB_[A-Z_]+)=", example, re.M)) @@ -827,9 +827,12 @@ def test_dev_script_reads_its_postgres_settings_from_dotenv(tmp_path): (root / "scripts").mkdir(parents=True) (root / "frontend" / "node_modules").mkdir(parents=True) shutil.copy(REPO / "scripts" / "dev.sh", root / "scripts" / "dev.sh") - (root / "backend").symlink_to(REPO / "backend") - (root / ".env").write_text("AGENTLAB_DEV_PG_CONTAINER=from-root-env\nAGENTLAB_DEV_PG_PORT=55491\n" - "AGENTLAB_DEV_PG_VOLUME='quoted-vol' \n") + (root / ".env").write_text("AGENTLAB_DEV_PG_CONTAINER=from-root-env\nAGENTLAB_DEV_PG_PORT = 55491 # 注释\n" + "export AGENTLAB_DEV_PG_VOLUME = 'quoted-vol' \nAGENTLAB_DATA_DIR=from-root\n") + # backend/.env 比 .env 优先(同后端):数据目录写相对路径,按后端的工作目录 backend/ 算 + (root / "backend").mkdir() + (root / "backend" / ".env").write_text("AGENTLAB_DATA_DIR = ../devdata\n") + (root / "backend" / "app").symlink_to(REPO / "backend" / "app") bin_dir = tmp_path / "bin" bin_dir.mkdir() log = tmp_path / "docker.log" @@ -839,7 +842,7 @@ def test_dev_script_reads_its_postgres_settings_from_dotenv(tmp_path): 'container) exit 1 ;; run) exit 1 ;; esac\nexit 0\n') for f in bin_dir.iterdir(): f.chmod(0o755) - env = {"PATH": f"{bin_dir}:/usr/bin:/bin:/usr/sbin:/sbin", "HOME": str(tmp_path), "AGENTLAB_DATA_DIR": str(tmp_path / "data"), + env = {"PATH": f"{bin_dir}:/usr/bin:/bin:/usr/sbin:/sbin", "HOME": str(tmp_path), "AGENTLAB_DEV_PG_IMAGE": "postgres:from-env", "AGENTLAB_PORT": "18799", "LANG": "C.UTF-8"} out = subprocess.run(["bash", str(root / "scripts" / "dev.sh"), "--postgres"], capture_output=True, text=True, env=env, timeout=60) @@ -848,7 +851,10 @@ def test_dev_script_reads_its_postgres_settings_from_dotenv(tmp_path): assert "--name from-root-env" in run and "127.0.0.1:55491:5432" in run, ".env 里的生效" assert "quoted-vol:/var/lib/postgresql/data" in run, "引号和行尾空白去掉" assert run.endswith("postgres:from-env"), "环境变量优先" - (root / "backend").unlink() + data = root / "backend" / ".." / "devdata" + assert f"{data}/.postgres-dev/password:/run/agentlab/password:ro" in run, "密码放在后端实际用的数据目录里" + assert (root / "devdata" / ".postgres-dev" / "pgpass").exists() + (root / "backend" / "app").unlink() def test_dev_script_refuses_unknown_arguments_and_documents_postgres(): diff --git a/scripts/dev.sh b/scripts/dev.sh index 4ed44005..328bafb4 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -73,19 +73,24 @@ fi export AGENTLAB_PORT="$API_PORT" # ---- --postgres:本地开发用的 PostgreSQL ---- -# 这几项(AGENTLAB_DEV_PG_*)只有这个脚本用。和后端的配置一样认 .env:环境变量 > backend/.env > .env > 缺省(后端的 settings -# 读这两个文件,后一个优先)。不 source:.env 是给后端按 KEY=VALUE 读的,不是 shell(值里的 $、空格、反引号不该被执行) +# 这几项(AGENTLAB_DEV_PG_*,还有数据目录 AGENTLAB_DATA_DIR)和后端的配置一样认 .env:环境变量 > backend/.env > .env > 缺省 +# (后端的 settings 读这两个文件,后一个优先)。写法照后端的 dotenv:KEY=value、KEY = value、export KEY=value、引号、行尾 # 注释。 +# 不 source:.env 是给后端按 KEY=VALUE 读的,不是 shell(值里的 $、空格、反引号不该被执行) dotenv_value() { - local key="$1" file line val="" found="" + local key="$1" file line name val="" found="" for file in "$ROOT/.env" "$ROOT/backend/.env"; do [ -f "$file" ] || continue while IFS= read -r line || [ -n "$line" ]; do line="${line%$'\r'}" line="${line#"${line%%[![:space:]]*}"}" line="${line#export }" - case "$line" in - "$key="*) val="${line#*=}"; found=1 ;; - esac + case "$line" in *=*) ;; *) continue ;; esac + name="${line%%=*}" + name="${name%"${name##*[![:space:]]}"}" + [ "$name" = "$key" ] || continue + val="${line#*=}" + val="${val#"${val%%[![:space:]]*}"}" + found=1 done < "$file" done [ -n "$found" ] || return 1 @@ -96,18 +101,20 @@ dotenv_value() { esac printf '%s' "$val" } -dev_pg_setting() { +dotenv_setting() { local value value="$(printenv "$1" 2>/dev/null || true)" [ -n "$value" ] || value="$(dotenv_value "$1" || true)" printf '%s' "${value:-$2}" } if [ -n "$WITH_PG" ]; then - PG_CONTAINER="$(dev_pg_setting AGENTLAB_DEV_PG_CONTAINER agentlab-dev-postgres)" - PG_PORT="$(dev_pg_setting AGENTLAB_DEV_PG_PORT 55432)" - PG_VOLUME="$(dev_pg_setting AGENTLAB_DEV_PG_VOLUME agentlab-dev-pgdata)" - PG_IMAGE="$(dev_pg_setting AGENTLAB_DEV_PG_IMAGE postgres:16)" - DEV_DATA="${AGENTLAB_DATA_DIR:-$ROOT/data}" + PG_CONTAINER="$(dotenv_setting AGENTLAB_DEV_PG_CONTAINER agentlab-dev-postgres)" + PG_PORT="$(dotenv_setting AGENTLAB_DEV_PG_PORT 55432)" + PG_VOLUME="$(dotenv_setting AGENTLAB_DEV_PG_VOLUME agentlab-dev-pgdata)" + PG_IMAGE="$(dotenv_setting AGENTLAB_DEV_PG_IMAGE postgres:16)" + # 密码文件放在后端实际用的数据目录里:和后端一样读 AGENTLAB_DATA_DIR,相对路径按后端的工作目录(backend/)算 + DEV_DATA="$(dotenv_setting AGENTLAB_DATA_DIR "$ROOT/data")" + case "$DEV_DATA" in /*) ;; *) DEV_DATA="$ROOT/backend/$DEV_DATA" ;; esac case "$PG_PORT" in ''|*[!0-9]*) echo "AGENTLAB_DEV_PG_PORT 要是端口号:$PG_PORT"; exit 2 ;; esac command -v docker >/dev/null || { echo "--postgres 要用 docker,没找到它"; exit 1; } docker version --format '{{.Server.Version}}' >/dev/null 2>&1 || { echo "连不上 docker 服务,先把 Docker 启动起来"; exit 1; }