From 484b7ddb95c4cfbfa5803449a9ba4d448e7d418d Mon Sep 17 00:00:00 2001 From: chiguyong Date: Thu, 2 Jul 2026 21:23:50 +0800 Subject: [PATCH] fix(dev): isolate dev environment ports and fix env loading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docker-compose.yaml: production mode uses expose (container-only) for Redis/PostgreSQL instead of ports (host-mapped) - docker-compose.dev.yml: dev override maps Redis 6381 and PostgreSQL 5435 to avoid conflicts with other projects (pms-redis 6379, geo_redis 6380, geo_db 5433) - config.py: fix empty env var handling — only skip .env override when os.environ[key] is non-empty; load .env, .env.dev, .env.local in sequence - scripts/dev-start.sh: manage agentkit-specific Docker containers - .gitignore: add .env.dev and .env.local (contain API keys) --- .gitignore | 2 + docker-compose.dev.yml | 27 +++++++++ docker-compose.yaml | 21 +++++-- scripts/dev-start.sh | 107 ++++++++++++++++------------------ src/agentkit/server/config.py | 12 +++- 5 files changed, 104 insertions(+), 65 deletions(-) create mode 100644 docker-compose.dev.yml diff --git a/.gitignore b/.gitignore index fd75d32..fae5625 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,8 @@ src/agentkit/server/static/ # Env .env +.env.dev +.env.local # Runtime data (auth DB, conversation DB, etc.) data/ diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..2399f60 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,27 @@ +version: "3.8" + +# ============================================================================= +# 开发环境 override +# ============================================================================= +# 用法:docker compose -f docker-compose.yaml -f docker-compose.dev.yml up -d redis postgres +# +# 仅启动 redis + postgres(agentkit 在宿主机运行),映射端口到 6381/5435 +# 避免与 pms-redis(6379) / geo_redis(6380) / geo_db(5433) 端口冲突 +# +# .env.dev 应包含: +# REDIS_URL=redis://127.0.0.1:6381/0 +# DATABASE_URL=postgresql+asyncpg://agentkit:agentkit@127.0.0.1:5435/agentkit +# ============================================================================= + +services: + # 开发模式不启动 agentkit 容器(在宿主机运行) + agentkit: + profiles: ["never"] + + redis: + ports: + - "6381:6379" + + postgres: + ports: + - "5435:5432" diff --git a/docker-compose.yaml b/docker-compose.yaml index 9d5cb34..8b0cbae 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,5 +1,12 @@ version: "3.8" +# ============================================================================= +# 生产部署配置 +# ============================================================================= +# 启动:docker compose up -d +# agentkit 容器内通过 service name (redis/postgres) 连接,不暴露中间件端口 +# ============================================================================= + services: agentkit: build: . @@ -8,8 +15,12 @@ services: - "8001:8001" env_file: .env environment: + # 容器间通信:使用 service name,不依赖宿主机端口 - REDIS_URL=redis://redis:6379/0 - DATABASE_URL=postgresql+asyncpg://agentkit:agentkit@postgres:5432/agentkit + - AGENTKIT_BUS_BACKEND=redis + - AGENTKIT_SESSION_BACKEND=redis + - AGENTKIT_TASK_STORE_BACKEND=redis depends_on: redis: condition: service_healthy @@ -25,8 +36,9 @@ services: redis: image: redis:7-alpine - ports: - - "6379:6379" + # 生产模式不暴露端口到宿主机,仅容器间可达 + expose: + - "6379" volumes: - redisdata:/data healthcheck: @@ -38,8 +50,9 @@ services: postgres: image: pgvector/pgvector:pg15 - ports: - - "5432:5432" + # 生产模式不暴露端口到宿主机,仅容器间可达 + expose: + - "5432" environment: POSTGRES_USER: agentkit POSTGRES_PASSWORD: agentkit diff --git a/scripts/dev-start.sh b/scripts/dev-start.sh index 1c11e45..a0e2108 100755 --- a/scripts/dev-start.sh +++ b/scripts/dev-start.sh @@ -17,6 +17,14 @@ # Python >= 3.11, Node.js >= 18, Redis, PostgreSQL (均自动检查) # --tauri 需要:Rust 工具链(rustup / brew install rust) # +# 端口(与 .env.dev 保持一致): +# 18001 — AgentKit 后端 API +# 18002 — Web GUI(前端 + 内置静态服务) +# 15173 — Vite 开发服务器(--tauri 模式) +# 15174 — Vite HMR websocket +# 6381 — Redis(agentkit 专属容器,避免与 pms-redis:6379 / geo_redis:6380 冲突) +# 5435 — PostgreSQL+pgvector(agentkit 专属容器,避免与 geo_db:5433 冲突) +# # ============================================================================= set -euo pipefail @@ -49,12 +57,14 @@ Fischer AgentKit — 本地开发环境启动 模式说明: 默认 Web 模式:agentkit gui (前后端 + 内置静态服务) - --tauri Tauri 模式:后端 API + Vite (:5173) + Tauri 桌面窗口 + --tauri Tauri 模式:后端 API + Vite (:15173) + Tauri 桌面窗口 端口映射: - 8000 — 后端 API - 8002 — Web GUI / 前端静态服务 - 5173 — Vite 开发服务器(--tauri 模式) + 18001 — 后端 API + 18002 — Web GUI / 前端静态服务 + 15173 — Vite 开发服务器(--tauri 模式) + 6381 — Redis(agentkit 专属容器) + 5435 — PostgreSQL+pgvector(agentkit 专属容器) EOF } @@ -112,11 +122,11 @@ print_status() { echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo -e "$([[ $S_DEPS -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_DEPS -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 依赖检查" echo -e "$([[ $S_ENV -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_ENV -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 环境配置" - echo -e "$([[ $S_REDIS -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_REDIS -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") Redis" - echo -e "$([[ $S_PG -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_PG -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") PostgreSQL" - echo -e "$([[ $S_BACKEND -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_BACKEND -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 后端服务 (:8000)" + echo -e "$([[ $S_REDIS -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_REDIS -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") Redis (:6381)" + echo -e "$([[ $S_PG -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_PG -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") PostgreSQL (:5435)" + echo -e "$([[ $S_BACKEND -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_BACKEND -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 后端服务 (:18001)" if [[ $MODE == "gui" || $MODE == "tauri" ]]; then - echo -e "$([[ $S_FRONTEND -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_FRONTEND -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 前端服务 (:8002)" + echo -e "$([[ $S_FRONTEND -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_FRONTEND -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 前端服务 (:18002)" fi if [[ $MODE == "tauri" ]]; then echo -e "$([[ $S_TAURI -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_TAURI -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") Tauri 客户端" @@ -180,30 +190,23 @@ check_redis() { section "检查 Redis" set_status redis 1 - if command -v redis-cli &>/dev/null && redis-cli ping 2>/dev/null | grep -q PONG; then - ok "Redis 运行中" + # agentkit 专属容器映射到 6381,避免与 pms-redis(6379) / geo_redis(6380) 冲突 + if redis-cli -h 127.0.0.1 -p 6381 ping 2>/dev/null | grep -q PONG; then + ok "Redis 运行中 (127.0.0.1:6381, agentkit 专属容器)" set_status redis 2 return 0 fi - # Docker 方式 - local name="fischer-redis-dev" - if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${name}$"; then - ok "Redis 容器运行中" - set_status redis 2 - return 0 - fi - - warn "Redis 未运行,尝试启动 Docker 容器..." - if docker run -d --name "$name" -p 6379:6379 redis:7-alpine &>/dev/null; then + warn "Redis 未运行 (6381),启动 agentkit 专属 Docker 容器..." + if docker compose -f docker-compose.yaml -f docker-compose.dev.yml up -d redis &>/dev/null; then sleep 2 - if docker exec "$name" redis-cli ping 2>/dev/null | grep -q PONG; then - ok "Redis Docker 容器启动成功 (:6379)" + if redis-cli -h 127.0.0.1 -p 6381 ping 2>/dev/null | grep -q PONG; then + ok "Redis Docker 容器启动成功 (127.0.0.1:6381)" set_status redis 2 return 0 fi fi - fail "Redis 启动失败(请确保 Docker 运行中,或手动启动 Redis)" + fail "Redis 启动失败(请确保 Docker 运行中)" set_status redis 3 return 1 } @@ -212,30 +215,18 @@ check_postgres() { section "检查 PostgreSQL" set_status pg 1 - if lsof -i :5432 2>/dev/null | grep -q LISTEN; then - ok "PostgreSQL 已在 :5432 监听" + # agentkit 专属容器映射到 5435,避免与 geo_db(5433) 冲突 + if PGPASSWORD=agentkit psql -h 127.0.0.1 -p 5435 -U agentkit -d agentkit -c "SELECT 1" &>/dev/null; then + ok "PostgreSQL 运行中 (127.0.0.1:5435, agentkit 专属容器)" set_status pg 2 return 0 fi - # Docker 方式 - local name="fischer-pg-dev" - if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${name}$"; then - ok "PostgreSQL Docker 容器运行中" - set_status pg 2 - return 0 - fi - - warn "PostgreSQL 未在 :5432 运行,尝试启动 Docker 容器..." - if docker run -d --name "$name" \ - -p 5432:5432 \ - -e POSTGRES_USER=agentkit \ - -e POSTGRES_PASSWORD=agentkit \ - -e POSTGRES_DB=agentkit \ - pgvector/pgvector:pg15 &>/dev/null; then + warn "PostgreSQL 未运行 (5435),启动 agentkit 专属 Docker 容器..." + if docker compose -f docker-compose.yaml -f docker-compose.dev.yml up -d postgres &>/dev/null; then sleep 3 - if docker exec "$name" pg_isready -U agentkit &>/dev/null; then - ok "PostgreSQL Docker 容器启动成功 (:5432)" + if PGPASSWORD=agentkit psql -h 127.0.0.1 -p 5435 -U agentkit -d agentkit -c "SELECT 1" &>/dev/null; then + ok "PostgreSQL Docker 容器启动成功 (127.0.0.1:5435)" set_status pg 2 return 0 fi @@ -281,17 +272,17 @@ start_backend() { section "启动后端服务" set_status backend 1 - info "启动后端 API (:8000)..." + info "启动后端 API (:18001)..." source .venv/bin/activate - agentkit serve --port 8000 & + agentkit serve --port 18001 & BACKEND_PID=$! # 等待健康检查就绪(最多 60 秒) info "等待后端就绪..." local attempt=0 while [[ $attempt -lt 60 ]]; do - if curl -sf http://127.0.0.1:8000/api/v1/health &>/dev/null; then - ok "后端 API 就绪 (http://127.0.0.1:8000, PID $BACKEND_PID)" + if curl -sf http://127.0.0.1:18001/api/v1/health &>/dev/null; then + ok "后端 API 就绪 (http://127.0.0.1:18001, PID $BACKEND_PID)" set_status backend 2 return 0 fi @@ -318,17 +309,17 @@ start_gui() { section "启动 Web GUI" set_status frontend 1 - info "启动 Web GUI (:8002)..." + info "启动 Web GUI (:18002)..." source .venv/bin/activate - agentkit gui --port 8002 & + agentkit gui --port 18002 & GUI_PID=$! # 等待就绪 info "等待 Web GUI 就绪..." local attempt=0 while [[ $attempt -lt 60 ]]; do - if curl -sf http://127.0.0.1:8002/api/v1/health &>/dev/null; then - ok "Web GUI 就绪 (http://127.0.0.1:8002, PID $GUI_PID)" + if curl -sf http://127.0.0.1:18002/api/v1/health &>/dev/null; then + ok "Web GUI 就绪 (http://127.0.0.1:18002, PID $GUI_PID)" set_status frontend 2 return 0 fi @@ -364,7 +355,7 @@ start_tauri() { fi info "启动 Tauri 桌面客户端..." - info " (Vite → :5173, 后端 API → :8000)" + info " (Vite → :15173, 后端 API → :18001)" cd "$FE_DIR" npm run tauri dev & TAURI_PID=$! @@ -400,7 +391,7 @@ stop_services() { echo "" info "正在停止所有服务..." - for port in 8000 8001 8002 5173; do + for port in 18001 18002 15173 15174; do local pid=$(lsof -ti :$port 2>/dev/null || true) if [[ -n "$pid" ]]; then kill $pid 2>/dev/null && ok "端口 $port 已停止" || true @@ -474,14 +465,14 @@ if [[ $FAILED -eq 0 ]]; then echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo "" if [[ $MODE == "gui" ]]; then - echo -e " Web GUI: ${GREEN}http://localhost:8002${NC}" - echo " (在浏览器中打开,或直接在 http://localhost:8002 访问)" + echo -e " Web GUI: ${GREEN}http://localhost:18002${NC}" + echo " (在浏览器中打开,或直接在 http://localhost:18002 访问)" elif [[ $MODE == "tauri" ]]; then - echo -e " 后端 API: ${GREEN}http://localhost:8000${NC}" - echo -e " Vite 热重载: ${GREEN}http://localhost:5173${NC}" + echo -e " 后端 API: ${GREEN}http://localhost:18001${NC}" + echo -e " Vite 热重载: ${GREEN}http://localhost:15173${NC}" echo " Tauri 桌面窗口应已自动打开" elif [[ $MODE == "serve" ]]; then - echo -e " 后端 API: ${GREEN}http://localhost:8000${NC}" + echo -e " 后端 API: ${GREEN}http://localhost:18001${NC}" fi echo "" echo -e " ${YELLOW}按 Ctrl+C 停止所有服务${NC}" @@ -491,7 +482,7 @@ else echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo "" echo -e " 诊断命令:" - echo -e " 查看日志: ${CYAN}curl http://127.0.0.1:8000/api/v1/health${NC}" + echo -e " 查看日志: ${CYAN}curl http://127.0.0.1:18001/api/v1/health${NC}" echo -e " 停止服务: ${CYAN}bash scripts/dev-stop.sh${NC}" fi echo "" diff --git a/src/agentkit/server/config.py b/src/agentkit/server/config.py index 55d4865..e634908 100644 --- a/src/agentkit/server/config.py +++ b/src/agentkit/server/config.py @@ -555,7 +555,11 @@ def load_dotenv( key, _, value = line.partition("=") key = key.strip() value = value.strip().strip("\"'") - if not key or key in os.environ: + # Skip only if key is set to a non-empty value in the environment. + # An empty/whitespace-only value (e.g. from a shell template like + # `${VAR:-}` that expanded to nothing) is treated as "not set" so + # subsequent .env files can still provide a real value. + if not key or (key in os.environ and os.environ[key].strip()): continue # Apply allowlist if provided if prefixes is not None or exact is not None: @@ -579,8 +583,10 @@ def load_config_with_dotenv(config_path: str | Path) -> ServerConfig: This is the canonical way to load config in all CLI commands and app factory. """ config_path = str(config_path) - dotenv = Path(config_path).parent / ".env" - load_dotenv(dotenv) + config_dir = Path(config_path).parent + # Load .env, .env.dev, .env.local in order (first non-empty value wins). + for candidate in (".env", ".env.dev", ".env.local"): + load_dotenv(config_dir / candidate) return ServerConfig.from_yaml(config_path)