mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 00:11:59 +00:00
配置模板补上 PostgreSQL 和多进程的几项:.env.example 的应用库一节分成应用库、连接池、PgBouncer、多进程、开发用 PostgreSQL 五组,补上 AGENTLAB_DATABASE_DIRECT_URL、AGENTLAB_DB_PGBOUNCER、AGENTLAB_DB_POOL_TIMEOUT、AGENTLAB_DB_POOL_RECYCLE、AGENTLAB_DB_STARTUP_TIMEOUT、AGENTLAB_WORKERS、AGENTLAB_WORKER_LEASE_S、AGENTLAB_WORKER_HEARTBEAT_S、AGENTLAB_SCHEDULER、AGENTLAB_SCHEDULER_TICK_S、AGENTLAB_BUILD 和 dev.sh --postgres 的 AGENTLAB_DEV_PG_*(都注释着,名字、缺省值照 core/config.py 和脚本);dev.sh 的 AGENTLAB_DEV_PG_* 也认 .env / backend/.env(环境变量优先,和后端的配置同一个规矩,不 source)。deploy/agentlab.env 末尾再追加「外接 PostgreSQL 与多进程(可选)」一段(地址写宿主机网桥 172.17.0.1、放行容器网段,和内置库二选一;断点库、PgBouncer、直连地址、工作进程、租约、连接池、启动时等库),已有的行一行没动。deploy.sh:AGENTLAB_DATABASE_DIRECT_URL 也算外接的库(和内置互斥),只设直连地址不设 AGENTLAB_DATABASE_URL、内置库开 AGENTLAB_DB_PGBOUNCER、SQLite 时 AGENTLAB_WORKERS 大于 1 都在起容器之前照实拦下(migrate-to-postgres 在停服务之前查)。单测:这些 AGENTLAB_* 经 --env-file 进容器、几条拦截、模板两段的项和缺省值、dev.sh 从 .env 读设置,以及守卫——config.py 里应用库、连接池、多进程、调度几类配置每一项都要在 .env.example 里有
This commit is contained in:
+38
-1
@@ -18,9 +18,46 @@
|
||||
# AGENTLAB_DATABASE_URL=postgresql+asyncpg://agentlab:密码@db.example.com:5432/agentlab?sslmode=verify-full
|
||||
# 断点放另一个库时才设(不设就和应用库同一个库)
|
||||
# AGENTLAB_CHECKPOINT_URL=postgresql://agentlab:密码@127.0.0.1:5432/agentlab_checkpoints
|
||||
# 连接池:每个进程常驻几条、高峰再多开几条;设成 0 不用连接池(前面有 PgBouncer 时)
|
||||
# 启动时库还连不上(和库一起起、库还在初始化)最多等几秒,按退避重试;密码不对、库不存在这种不等。缺省 90
|
||||
# AGENTLAB_DB_STARTUP_TIMEOUT=90
|
||||
|
||||
# ---- 连接池(PostgreSQL;每个进程一套)----
|
||||
# 常驻几条,缺省 10;设成 0 不用连接池、每次新建连接
|
||||
# AGENTLAB_DB_POOL_SIZE=10
|
||||
# 高峰再多开几条,缺省 20
|
||||
# AGENTLAB_DB_MAX_OVERFLOW=20
|
||||
# 池子满了等一条空出来最多等几秒,缺省 30
|
||||
# AGENTLAB_DB_POOL_TIMEOUT=30
|
||||
# 一条连接用多久换新的(秒),缺省 1800
|
||||
# AGENTLAB_DB_POOL_RECYCLE=1800
|
||||
|
||||
# ---- PgBouncer(README「多进程」的「直连与 PgBouncer」)----
|
||||
# 直连地址(写法同 AGENTLAB_DATABASE_URL):进程间通知、跨进程锁、断点这几样依赖会话状态的连接走它,普通查询走上面的地址。
|
||||
# 不设就全用 AGENTLAB_DATABASE_URL(直连 PostgreSQL、或 PgBouncer 的会话模式时都不用设)
|
||||
# AGENTLAB_DATABASE_DIRECT_URL=postgresql+asyncpg://agentlab:密码@db.example.com:5432/agentlab
|
||||
# AGENTLAB_DATABASE_URL 指的是 PgBouncer 的事务模式时设成 1(缺省 0):不用客户端连接池、关掉预备语句缓存;
|
||||
# 这时必须另给上面的直连地址,没给就拒绝启动
|
||||
# AGENTLAB_DB_PGBOUNCER=0
|
||||
|
||||
# ---- 多进程(README「多进程」;只有应用库是 PostgreSQL 时才能大于 1)----
|
||||
# 几个工作进程,缺省 1(scripts/prod.sh、Docker 的入口按它起;dev.sh 总是一个进程、热重载)
|
||||
# AGENTLAB_WORKERS=1
|
||||
# 每个进程在库里登记、续租约:租约多长(秒,缺省 30)、隔多久续一次(秒,缺省 5)。租约到期没续上算进程没了,别的进程接手它的活
|
||||
# AGENTLAB_WORKER_LEASE_S=30
|
||||
# AGENTLAB_WORKER_HEARTBEAT_S=5
|
||||
# 后台调度循环(定时任务、事件派发、流程时限)开不开,缺省开;拷一份数据目录另起的服务要关成 0。一拍隔几秒,缺省 15
|
||||
# AGENTLAB_SCHEDULER=1
|
||||
# AGENTLAB_SCHEDULER_TICK_S=15
|
||||
# 构建号:进程登记的代码版本里带上它(新旧版本不许同时连一个库)。Docker 镜像构建时自动写进去,不设就用 git 提交
|
||||
# AGENTLAB_BUILD=
|
||||
|
||||
# ---- 开发用 PostgreSQL(./scripts/dev.sh --postgres)----
|
||||
# 这几项只有 dev.sh 读:写成环境变量或写在这个 .env 里都行(环境变量优先)。数据在 docker 数据卷里,
|
||||
# 密码第一次自动生成在 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
|
||||
# AGENTLAB_DEV_PG_IMAGE=postgres:16
|
||||
|
||||
# ---- 沙箱 ----
|
||||
# auto(挑第一个已就绪的:microvm → bubblewrap → seatbelt → local)
|
||||
|
||||
@@ -1079,7 +1079,7 @@ AGENTLAB_DATABASE_URL=postgresql+asyncpg://agentlab:<密码>@127.0.0.1:5432/agen
|
||||
`postgres:16` 容器 `agentlab-dev-postgres`,只开在 `127.0.0.1:55432`,数据在 docker 数据卷 `agentlab-dev-pgdata`,密码第一次
|
||||
自动生成在 `data/.postgres-dev/`(后端按 `PGPASSFILE` 读),后端照它设 `AGENTLAB_DATABASE_URL`;Ctrl-C 只停前后端,库的容器
|
||||
留着下次复用。容器名、端口、数据卷、镜像用 `AGENTLAB_DEV_PG_CONTAINER`、`AGENTLAB_DEV_PG_PORT`、`AGENTLAB_DEV_PG_VOLUME`、
|
||||
`AGENTLAB_DEV_PG_IMAGE` 改。不加 `--postgres` 和原来一样:SQLite,不用 docker。
|
||||
`AGENTLAB_DEV_PG_IMAGE` 改(环境变量,或写在 `.env` 里,环境变量优先;`.env.example` 列着应用库、连接池、PgBouncer、多进程和这几项)。不加 `--postgres` 和原来一样:SQLite,不用 docker。
|
||||
|
||||
**从 SQLite 搬过来。** `python -m app.db copy-to-postgres` 把数据目录里的 `agentlab.db` 和 `checkpoints.db` 导进 PostgreSQL,
|
||||
停在审批上的、中断了的运行导过去之后照样能接着跑:
|
||||
|
||||
@@ -238,6 +238,60 @@ def test_install_postgres_on_a_sqlite_data_dir_points_to_migrate(deploy):
|
||||
assert deploy.containers() == {} and deploy.mode() == "sqlite"
|
||||
|
||||
|
||||
def test_agentlab_settings_reach_the_container_through_the_env_file(tmp_path):
|
||||
"""agentlab.env 里的 AGENTLAB_*(多进程、连接池……)经 --env-file 原样进应用容器;内置库的地址、密码文件另用 -e 给。"""
|
||||
deploy = Deploy(tmp_path, env_lines=["AGENTLAB_WORKERS=3", "AGENTLAB_DB_POOL_SIZE=5", "AGENTLAB_WORKER_LEASE_S=40"])
|
||||
deploy("install", "--postgres")
|
||||
calls = deploy.calls()
|
||||
app_args = calls[index_of(calls, runs_named(APP))]
|
||||
env_file = Path(app_args[app_args.index("--env-file") + 1])
|
||||
assert env_file == deploy.pkg / "agentlab.env"
|
||||
lines = env_file.read_text().splitlines()
|
||||
assert {"AGENTLAB_WORKERS=3", "AGENTLAB_DB_POOL_SIZE=5", "AGENTLAB_WORKER_LEASE_S=40"} <= set(lines)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("line", ["AGENTLAB_DATABASE_URL=postgresql+asyncpg://u:p@db.example:5432/agentlab",
|
||||
"AGENTLAB_CHECKPOINT_URL=postgresql://u:p@db.example:5432/ckpt"])
|
||||
def test_any_external_database_setting_conflicts_with_builtin(tmp_path, line):
|
||||
deploy = Deploy(tmp_path, env_lines=[line])
|
||||
out = deploy("install", "--postgres", ok=False)
|
||||
assert "两种库只能选一种" in out.stderr and line.split("=")[0] in out.stderr
|
||||
assert deploy.containers() == {}
|
||||
|
||||
|
||||
def test_direct_url_needs_the_external_url(tmp_path):
|
||||
deploy = Deploy(tmp_path, env_lines=["AGENTLAB_DATABASE_DIRECT_URL=postgresql+asyncpg://u:p@db.example:5432/agentlab"])
|
||||
out = deploy("install", ok=False)
|
||||
assert "AGENTLAB_DATABASE_DIRECT_URL" in out.stderr and "要和 AGENTLAB_DATABASE_URL 一起设" in out.stderr
|
||||
assert deploy.containers() == {}
|
||||
out = deploy("install", "--postgres", ok=False)
|
||||
assert "两种库只能选一种" in out.stderr, "和内置库一起:照互斥报"
|
||||
|
||||
|
||||
def test_pgbouncer_switch_is_refused_for_the_builtin_database(tmp_path):
|
||||
deploy = Deploy(tmp_path, env_lines=["AGENTLAB_DB_PGBOUNCER=1"])
|
||||
out = deploy("install", "--postgres", ok=False)
|
||||
assert "内置 PostgreSQL 是直连" in out.stderr and deploy.containers() == {}
|
||||
make_sqlite_data(deploy.pkg / "data")
|
||||
out = deploy("migrate-to-postgres", ok=False)
|
||||
assert "内置 PostgreSQL 是直连" in out.stderr
|
||||
assert not any(a[:1] == ["stop"] for a in deploy.calls()), "切换之前就拦下,服务没停"
|
||||
# 外接的库配 PgBouncer:照常
|
||||
deploy.write_env(["AGENTLAB_DB_PGBOUNCER=1", "AGENTLAB_DATABASE_URL=postgresql+asyncpg://u:p@bouncer.example:6432/agentlab",
|
||||
"AGENTLAB_DATABASE_DIRECT_URL=postgresql+asyncpg://u:p@db.example:5432/agentlab"])
|
||||
deploy("install")
|
||||
assert "外接 PostgreSQL" in deploy("status").stdout
|
||||
|
||||
|
||||
def test_more_than_one_worker_needs_postgres(tmp_path):
|
||||
deploy = Deploy(tmp_path, env_lines=["AGENTLAB_WORKERS=4"])
|
||||
out = deploy("install", ok=False)
|
||||
assert "SQLite 只支持单进程" in out.stderr and "migrate-to-postgres" in out.stderr
|
||||
assert deploy.containers() == {}
|
||||
deploy("install", "--postgres")
|
||||
assert deploy.containers()[APP]["state"] == "running", "内置 PostgreSQL 时可以多进程"
|
||||
|
||||
|
||||
def test_install_postgres_refuses_a_leftover_volume(deploy):
|
||||
deploy.state_file.write_text(json.dumps({"images": {}, "containers": {}, "networks": [], "volumes": [VOL]}))
|
||||
out = deploy("install", "--postgres", ok=False)
|
||||
@@ -553,8 +607,10 @@ def test_package_script_takes_the_postgres_image_from_deploy_sh():
|
||||
def test_env_template_lists_the_postgres_settings_commented_out_with_the_defaults():
|
||||
"""agentlab.env 模板末尾的「内置 PostgreSQL(可选)」:几项都注释着(不改任何人的部署),缺省值和 deploy.sh 里的一致。"""
|
||||
template = (REPO / "deploy" / "agentlab.env").read_text(encoding="utf-8")
|
||||
section = template[template.index("# ---- 内置 PostgreSQL(可选)----"):]
|
||||
assert template.rstrip().endswith(section.rstrip()), "追加在文件末尾"
|
||||
start = template.index("# ---- 内置 PostgreSQL(可选)----")
|
||||
tail = template[start:]
|
||||
assert template.rstrip().endswith(tail.rstrip()), "追加在文件末尾"
|
||||
section = tail.split("\n# ---- 外接 PostgreSQL 与多进程(可选)----")[0]
|
||||
commented = dict(line[2:].split("=", 1) for line in section.splitlines()
|
||||
if line.startswith("# ") and "=" in line and line[2:3].isupper())
|
||||
assert commented == {"POSTGRES_IMAGE": PG_IMAGE, "POSTGRES_CONTAINER": "agentlab-postgres",
|
||||
@@ -563,6 +619,29 @@ def test_env_template_lists_the_postgres_settings_commented_out_with_the_default
|
||||
"都是注释"
|
||||
|
||||
|
||||
def test_env_template_ends_with_the_external_postgres_and_multiprocess_settings():
|
||||
"""模板最后一段「外接 PostgreSQL 与多进程(可选)」:要的几项都在、都注释着,缺省值和代码里的一致。"""
|
||||
from app.core.config import Settings
|
||||
|
||||
template = (REPO / "deploy" / "agentlab.env").read_text(encoding="utf-8")
|
||||
section = template[template.index("# ---- 外接 PostgreSQL 与多进程(可选)----"):]
|
||||
commented = dict(line[2:].split("=", 1) for line in section.splitlines()
|
||||
if line.startswith("# AGENTLAB_") and "=" in line)
|
||||
assert set(commented) == {
|
||||
"AGENTLAB_DATABASE_URL", "AGENTLAB_CHECKPOINT_URL", "AGENTLAB_DB_PGBOUNCER", "AGENTLAB_DATABASE_DIRECT_URL",
|
||||
"AGENTLAB_WORKERS", "AGENTLAB_WORKER_LEASE_S", "AGENTLAB_WORKER_HEARTBEAT_S", "AGENTLAB_DB_POOL_SIZE",
|
||||
"AGENTLAB_DB_MAX_OVERFLOW", "AGENTLAB_DB_POOL_TIMEOUT", "AGENTLAB_DB_POOL_RECYCLE", "AGENTLAB_DB_STARTUP_TIMEOUT"}
|
||||
assert "172.17.0.1" in commented["AGENTLAB_DATABASE_URL"] and "172.17.0.0/16" in section
|
||||
fields = Settings.model_fields
|
||||
for key, shown in commented.items():
|
||||
default = fields[key.removeprefix("AGENTLAB_").lower()].default
|
||||
if isinstance(default, bool):
|
||||
assert shown == str(int(default)), key
|
||||
elif isinstance(default, (int, float)):
|
||||
assert float(shown) == float(default), key
|
||||
assert not any(line.startswith("AGENTLAB_") for line in section.splitlines()), "都是注释"
|
||||
|
||||
|
||||
def test_settings_written_in_agentlab_env_take_effect(tmp_path):
|
||||
deploy = Deploy(tmp_path, env_lines=["POSTGRES_CONTAINER=agl-db", "DOCKER_NETWORK=agl-n", "POSTGRES_VOLUME=agl-v",
|
||||
"BACKUP_KEEP=1"])
|
||||
@@ -572,6 +651,55 @@ def test_settings_written_in_agentlab_env_take_effect(tmp_path):
|
||||
assert deploy.containers()[APP]["network"] == "agl-n"
|
||||
|
||||
|
||||
def test_env_example_lists_every_database_pool_and_multiprocess_setting():
|
||||
"""core/config.py 里应用库、连接池、PgBouncer、多进程、调度这几类配置,每一项都在 .env.example 里有一行(注释掉的示例);
|
||||
dev.sh --postgres 读的 AGENTLAB_DEV_PG_* 也在。以后加了配置忘了写,这里会报出来。"""
|
||||
import re
|
||||
|
||||
from app.core.config import Settings
|
||||
|
||||
prefixes = ("database_", "checkpoint_", "db_", "worker", "scheduler")
|
||||
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()))
|
||||
assert len(dev) == 4
|
||||
example = (REPO / ".env.example").read_text(encoding="utf-8")
|
||||
listed = set(re.findall(r"^# (AGENTLAB_[A-Z_]+)=", example, re.M))
|
||||
assert names | dev <= listed, f".env.example 里缺:{sorted((names | dev) - listed)}"
|
||||
|
||||
|
||||
def test_dev_script_reads_its_postgres_settings_from_dotenv(tmp_path):
|
||||
"""dev.sh --postgres 的几项:环境变量优先,没设时读 .env / backend/.env(后一个优先),和后端的配置同一个规矩。
|
||||
在一份拷出来的脚本上跑,假的 conda、pnpm、docker:docker run 记下参数就失败退出,不真起容器、不起前后端。"""
|
||||
root = tmp_path / "repo"
|
||||
(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")
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
log = tmp_path / "docker.log"
|
||||
(bin_dir / "conda").write_text(f'#!/bin/sh\ncase "$1" in env) echo "agentlab /x" ;; run) echo "{sys.executable}" ;; esac\n')
|
||||
(bin_dir / "pnpm").write_text("#!/bin/sh\nexit 0\n")
|
||||
(bin_dir / "docker").write_text(f'#!/bin/sh\necho "$*" >> "{log}"\ncase "$1" in version) echo 27 ;; '
|
||||
'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"),
|
||||
"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)
|
||||
assert out.returncode != 0, "假的 docker run 失败,脚本停在这里"
|
||||
[run] = [line for line in log.read_text().splitlines() if line.startswith("run ")]
|
||||
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()
|
||||
|
||||
|
||||
def test_dev_script_refuses_unknown_arguments_and_documents_postgres():
|
||||
env = {**os.environ, "PATH": "/usr/bin:/bin"}
|
||||
out = subprocess.run(["bash", str(REPO / "scripts" / "dev.sh"), "--bogus"], capture_output=True, text=True, env=env)
|
||||
|
||||
+5
-2
@@ -216,7 +216,9 @@ AGENTLAB_DATABASE_URL=postgresql+asyncpg://agentlab:<密码>@<数据库地址>:5
|
||||
|
||||
容器里的 `127.0.0.1` 是容器自己:数据库在别的机器上就写那台的地址;就在这台机器上,写宿主机在 docker 网桥上的地址
|
||||
(一般是 `172.17.0.1`),并让 PostgreSQL 监听这个地址、`pg_hba.conf` 放行容器的网段。执行断点也进同一个库。
|
||||
表结构和默认数据同样在每次启动时自动补齐,库暂时连不上时同样按退避重试。
|
||||
表结构和默认数据同样在每次启动时自动补齐,库暂时连不上时同样按退避重试。`agentlab.env` 末尾「外接 PostgreSQL 与多进程(可选)」
|
||||
一段列着这几项(地址、断点库、PgBouncer、工作进程、连接池、启动时等库),都注释着,要用的去掉行首的 `#`;它们和别的 `AGENTLAB_*`
|
||||
一样经 `--env-file` 原样进容器。
|
||||
|
||||
已经在用 SQLite 的,先把数据导过去(同一个镜像里带着导入命令;`--dry-run` 只核对、不写):
|
||||
|
||||
@@ -241,7 +243,8 @@ docker run --rm --user 10001:10001 -v "$(pwd)/data:/data" --env-file agentlab.en
|
||||
问数据和后台任务(规矩见仓库 README「多进程」)。SQLite 时设大于 1 会启动失败(照实报错:SQLite 只支持单进程)。
|
||||
几台机器、几个容器指着同一个库也行,但数据目录要放在共享存储上、每台挂在同一个路径,限制见仓库 README「多进程」。
|
||||
|
||||
内置 PostgreSQL 是直连(中间没有 PgBouncer),多进程时什么都不用另设(不用 `AGENTLAB_DATABASE_DIRECT_URL`)。新旧版本的进程不能同时
|
||||
内置 PostgreSQL 是直连(中间没有 PgBouncer),多进程时什么都不用另设(不用 `AGENTLAB_DATABASE_DIRECT_URL`;设了它、或者
|
||||
`AGENTLAB_DB_PGBOUNCER=1`,deploy.sh 照实拦下)。库还是 SQLite 时 `AGENTLAB_WORKERS` 大于 1,deploy.sh 在起容器之前就拦下。新旧版本的进程不能同时
|
||||
连着一个库(代码里拦着:新版本发现库里还有活着的旧版本进程就拒绝启动)。`./deploy.sh upgrade` 就是按这个顺序做的:先停掉旧容器
|
||||
(里面的全部工作进程一起停)、备份,再起新版本;旧进程要是被强杀、登记还没过期,新版本启动时会等它过期(最多半分钟左右)。
|
||||
同一个库还接着别处的 AgentLab 进程(另一台机器、另一个容器)的,升级前先把它们也停掉。
|
||||
|
||||
@@ -128,3 +128,29 @@ ENABLE_BWRAP=0
|
||||
# POSTGRES_VOLUME=agentlab-pgdata
|
||||
# upgrade 前自动备份库(SQLite 拷到 backups/sqlite/,内置 PostgreSQL 用 pg_dump 存到 backups/postgres/),只留最近几份
|
||||
# BACKUP_KEEP=5
|
||||
|
||||
# ---- 外接 PostgreSQL 与多进程(可选)----
|
||||
# 库放在你自己的 PostgreSQL 上(库要先建好):去掉 AGENTLAB_DATABASE_URL 行首的 # 填好地址,再 ./deploy.sh install。
|
||||
# 和上面的内置 PostgreSQL 只能选一种:两个都用时 deploy.sh 照实报错、不起容器。容器里的 127.0.0.1 是容器自己:库就在这台机器上时
|
||||
# 写宿主机在 docker 网桥上的地址(一般是 172.17.0.1),并让 PostgreSQL 监听这个地址、pg_hba.conf 放行容器的网段(一般是
|
||||
# 172.17.0.0/16);库在别的机器上就写那台的地址。下面这些 AGENTLAB_* 都原样传进容器(docker run --env-file 这个文件)
|
||||
# AGENTLAB_DATABASE_URL=postgresql+asyncpg://agentlab:密码@172.17.0.1:5432/agentlab
|
||||
# 断点放另一个库时才设(不设就和应用库同一个库)
|
||||
# AGENTLAB_CHECKPOINT_URL=postgresql://agentlab:密码@172.17.0.1:5432/agentlab_checkpoints
|
||||
# 前面有 PgBouncer 的事务模式时:AGENTLAB_DB_PGBOUNCER 设成 1,并另给直连 PostgreSQL 的地址(会话模式、直连都不用这两项)。
|
||||
# 内置 PostgreSQL 是直连,设了 deploy.sh 会拦下
|
||||
# AGENTLAB_DB_PGBOUNCER=0
|
||||
# AGENTLAB_DATABASE_DIRECT_URL=postgresql+asyncpg://agentlab:密码@172.17.0.1:5432/agentlab
|
||||
# 容器里起几个工作进程,缺省 1。只有用 PostgreSQL(内置或外接)时才能大于 1:SQLite 时 deploy.sh 先拦下
|
||||
# AGENTLAB_WORKERS=1
|
||||
# 工作进程在库里的租约多长、隔多久续一次(秒,缺省 30、5)。租约到期没续上算进程没了,别的进程接手它的活
|
||||
# AGENTLAB_WORKER_LEASE_S=30
|
||||
# AGENTLAB_WORKER_HEARTBEAT_S=5
|
||||
# 连接池(每个工作进程一套):常驻几条、高峰再多开几条、等空闲连接最多几秒、一条连接用多久换新(缺省 10、20、30、1800 秒)。
|
||||
# 进程多了连接数跟着乘,PostgreSQL 的 max_connections 要够(见 README「用 PostgreSQL」的连接数的账)
|
||||
# AGENTLAB_DB_POOL_SIZE=10
|
||||
# AGENTLAB_DB_MAX_OVERFLOW=20
|
||||
# AGENTLAB_DB_POOL_TIMEOUT=30
|
||||
# AGENTLAB_DB_POOL_RECYCLE=1800
|
||||
# 启动时库还没就绪最多等几秒(按退避重试,内置、外接都一样),缺省 90
|
||||
# AGENTLAB_DB_STARTUP_TIMEOUT=90
|
||||
|
||||
+44
-3
@@ -141,8 +141,13 @@ POSTGRES_CONTAINER=""
|
||||
DOCKER_NETWORK=""
|
||||
POSTGRES_VOLUME=""
|
||||
BACKUP_KEEP="5"
|
||||
# agentlab.env 里设了外接的库(AGENTLAB_DATABASE_URL)没有:只记有没有,值(带密码)不读进来
|
||||
# agentlab.env 里设了外接的库(AGENTLAB_DATABASE_URL 等)没有:只记设了哪一项,值(带密码)不读进来
|
||||
EXTERNAL_DB=""
|
||||
EXTERNAL_URL_SET=""
|
||||
DIRECT_URL_SET=""
|
||||
# 这两项要看值:PgBouncer 事务模式的开关(内置库是直连,开了就拦下)、工作进程数(SQLite 只能是 1)。都原样经 --env-file 进容器
|
||||
PGBOUNCER_VALUE=""
|
||||
WORKERS_VALUE=""
|
||||
# 内置 PostgreSQL 里的库名、用户名(PostgreSQL 容器第一次初始化时按它建)
|
||||
PG_DB="agentlab"
|
||||
PG_USER="agentlab"
|
||||
@@ -163,8 +168,16 @@ read_env() {
|
||||
HOST_PORT|BIND_ADDR|DATA_DIR|CONTAINER_NAME|ENABLE_BWRAP|ENABLE_MICROVM|POSTGRES_IMAGE|POSTGRES_CONTAINER|\
|
||||
DOCKER_NETWORK|POSTGRES_VOLUME|BACKUP_KEEP)
|
||||
printf -v "$key" '%s' "$(unquote "$(trim "$val")")" ;;
|
||||
AGENTLAB_DATABASE_URL|AGENTLAB_CHECKPOINT_URL)
|
||||
[ -z "$(trim "$val")" ] || EXTERNAL_DB="$key" ;;
|
||||
AGENTLAB_DATABASE_URL|AGENTLAB_CHECKPOINT_URL|AGENTLAB_DATABASE_DIRECT_URL)
|
||||
if [ -n "$(trim "$val")" ]; then
|
||||
[ -n "$EXTERNAL_DB" ] || EXTERNAL_DB="$key"
|
||||
case "$key" in
|
||||
AGENTLAB_DATABASE_URL) EXTERNAL_URL_SET=1 ;;
|
||||
AGENTLAB_DATABASE_DIRECT_URL) DIRECT_URL_SET=1 ;;
|
||||
esac
|
||||
fi ;;
|
||||
AGENTLAB_DB_PGBOUNCER) PGBOUNCER_VALUE="$(unquote "$(trim "$val")")" ;;
|
||||
AGENTLAB_WORKERS) WORKERS_VALUE="$(unquote "$(trim "$val")")" ;;
|
||||
AGENTLAB_DATA_DIR|AGENTLAB_WEB_DIST|AGENTLAB_PORT|AGENTLAB_HOST)
|
||||
die "agentlab.env 第 $n 行:不要设 ${key}。容器里的路径和端口是固定的,改宿主机这边用 DATA_DIR、HOST_PORT、BIND_ADDR" ;;
|
||||
*)
|
||||
@@ -606,6 +619,7 @@ PostgreSQL,运行 ./deploy.sh migrate-to-postgres"
|
||||
else
|
||||
MODE="sqlite"
|
||||
fi
|
||||
check_db_settings
|
||||
DB_OPTS=()
|
||||
if [ "$MODE" = builtin ]; then
|
||||
DB_OPTS=(--network "$DOCKER_NETWORK"
|
||||
@@ -614,6 +628,31 @@ PostgreSQL,运行 ./deploy.sh migrate-to-postgres"
|
||||
fi
|
||||
}
|
||||
|
||||
# agentlab.env 里和库有关的几项是否和用的哪种库对得上(resolve_mode 定了 MODE 之后查;migrate-to-postgres 按切过去的内置库查)
|
||||
pgbouncer_on() {
|
||||
case "$(printf '%s' "$PGBOUNCER_VALUE" | tr '[:upper:]' '[:lower:]')" in 1|true|yes|on) return 0 ;; *) return 1 ;; esac
|
||||
}
|
||||
|
||||
check_db_settings() {
|
||||
local mode="${1:-$MODE}"
|
||||
if [ -n "$DIRECT_URL_SET" ] && [ -z "$EXTERNAL_URL_SET" ]; then
|
||||
die "agentlab.env 里设了 AGENTLAB_DATABASE_DIRECT_URL,却没设 AGENTLAB_DATABASE_URL:直连地址是给外接 PostgreSQL 配 PgBouncer 用的,
|
||||
要和 AGENTLAB_DATABASE_URL 一起设。不用外接的库就把它删掉"
|
||||
fi
|
||||
if [ "$mode" = builtin ] && pgbouncer_on; then
|
||||
die "agentlab.env 里 AGENTLAB_DB_PGBOUNCER=${PGBOUNCER_VALUE},但内置 PostgreSQL 是直连、中间没有 PgBouncer。把这一行删掉或改成 0"
|
||||
fi
|
||||
case "$WORKERS_VALUE" in
|
||||
''|*[!0-9]*) ;;
|
||||
*)
|
||||
if [ "$mode" = sqlite ] && [ "$WORKERS_VALUE" -gt 1 ]; then
|
||||
die "agentlab.env 里 AGENTLAB_WORKERS=${WORKERS_VALUE},但库是 SQLite:SQLite 只支持单进程,服务会拒绝启动。
|
||||
- 改回 1(或删掉这一行);
|
||||
- 要多进程,先换成 PostgreSQL:./deploy.sh migrate-to-postgres(内置)或设 AGENTLAB_DATABASE_URL(外接)"
|
||||
fi ;;
|
||||
esac
|
||||
}
|
||||
|
||||
mode_label() {
|
||||
case "$MODE" in
|
||||
builtin) echo "内置 PostgreSQL(容器 ${POSTGRES_CONTAINER},数据卷 ${POSTGRES_VOLUME})" ;;
|
||||
@@ -918,6 +957,8 @@ cmd_migrate_to_postgres() {
|
||||
check_existing
|
||||
[ -z "$EXTERNAL_DB" ] || die "agentlab.env 里设了 ${EXTERNAL_DB}(外接 PostgreSQL)。内置和外接只能选一种:要切到内置的,先删掉那一行;
|
||||
要搬进外接的库,用镜像里的 python -m app.db copy-to-postgres(见 README「用 PostgreSQL」)"
|
||||
# 切过去就是内置库:和它对不上的设置(PgBouncer 开关)先拦下,别停了服务才发现
|
||||
check_db_settings builtin
|
||||
local state
|
||||
state="$(stored_state)"
|
||||
if [ "$state" = builtin ]; then
|
||||
|
||||
+35
-5
@@ -13,7 +13,8 @@ usage() {
|
||||
--postgres 应用库用 PostgreSQL:起(已经有就复用)一个本地开发用的 PostgreSQL 容器,端口只开在 127.0.0.1,
|
||||
后端照它设 AGENTLAB_DATABASE_URL。密码第一次自动生成,在 data/.postgres-dev/ 里;库的数据在 docker
|
||||
数据卷里,Ctrl-C 只停前后端,库的容器留着下次复用(不要了:docker rm -f 容器名 && docker volume rm 卷名)。
|
||||
不加就和原来一样:SQLite,不用 docker。下面几个环境变量可以改缺省:
|
||||
不加就和原来一样:SQLite,不用 docker。下面几项可以改缺省(环境变量,或者写在 .env / backend/.env 里,
|
||||
环境变量优先):
|
||||
AGENTLAB_DEV_PG_CONTAINER 容器名,缺省 agentlab-dev-postgres
|
||||
AGENTLAB_DEV_PG_PORT 本机端口,缺省 55432
|
||||
AGENTLAB_DEV_PG_VOLUME 数据卷,缺省 agentlab-dev-pgdata
|
||||
@@ -72,11 +73,40 @@ fi
|
||||
export AGENTLAB_PORT="$API_PORT"
|
||||
|
||||
# ---- --postgres:本地开发用的 PostgreSQL ----
|
||||
# 这几项(AGENTLAB_DEV_PG_*)只有这个脚本用。和后端的配置一样认 .env:环境变量 > backend/.env > .env > 缺省(后端的 settings
|
||||
# 读这两个文件,后一个优先)。不 source:.env 是给后端按 KEY=VALUE 读的,不是 shell(值里的 $、空格、反引号不该被执行)
|
||||
dotenv_value() {
|
||||
local key="$1" file line 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
|
||||
done < "$file"
|
||||
done
|
||||
[ -n "$found" ] || return 1
|
||||
val="${val%"${val##*[![:space:]]}"}"
|
||||
case "$val" in
|
||||
\"*\"|\'*\') val="${val:1:${#val}-2}" ;;
|
||||
*) val="${val%%[[:space:]]#*}"; val="${val%"${val##*[![:space:]]}"}" ;;
|
||||
esac
|
||||
printf '%s' "$val"
|
||||
}
|
||||
dev_pg_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="${AGENTLAB_DEV_PG_CONTAINER:-agentlab-dev-postgres}"
|
||||
PG_PORT="${AGENTLAB_DEV_PG_PORT:-55432}"
|
||||
PG_VOLUME="${AGENTLAB_DEV_PG_VOLUME:-agentlab-dev-pgdata}"
|
||||
PG_IMAGE="${AGENTLAB_DEV_PG_IMAGE:-postgres:16}"
|
||||
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}"
|
||||
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; }
|
||||
|
||||
Reference in New Issue
Block a user