#!/usr/bin/env bash # ============================================================================= # Chat-Agent-System 多环境与服务管理脚本 (env-service.sh) # # 支持环境: # shared : 共享知识库基础设施 (pgvector / regulations-db) # dev : 开发环境服务 (端口 8080, DB 5433, 数据隔离 ./data/dev) # test : 测试环境服务 (端口 8081, DB 5434, 数据隔离 ./data/test) # main : 生产环境服务 (端口 8000, DB 5435, 数据隔离 ./data/main) # all : 所有环境 # # 支持命令: start | stop | restart | rebuild | status | logs | down # ============================================================================= set -eo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" cd "${ROOT_DIR}" ENV_TARGET="$1" ACTION="$2" show_usage() { cat < 示例: $0 shared start # 启动共享知识库基础服务 $0 dev start # 启动开发分支环境 $0 dev rebuild # 重建镜像并重建容器(合入代码后使用) $0 test start # 启动测试分支环境 $0 main start # 启动主分支生产环境 $0 all status # 查看所有环境服务状态 $0 dev logs # 查看开发环境日志 $0 dev stop # 停止开发环境 EOF exit 1 } if [[ -z "${ENV_TARGET}" || -z "${ACTION}" ]]; then show_usage fi # 确保环境变量配置文件与存储目录存在 prepare_env_file() { local target="$1" local env_file="configs/.env.${target}" local env_example="configs/.env.${target}.example" if [[ ! -f "${env_file}" ]]; then if [[ -f "${env_example}" ]]; then echo "[提示] 配置文件 ${env_file} 不存在,正在从 ${env_example} 自动初始化..." cp "${env_example}" "${env_file}" fi fi # 确保隔离数据目录存在 # 确保隔离数据目录与法规目录存在 if [[ "${target}" == "shared" ]]; then mkdir -p "data/shared/kb" mkdir -p ".regulations/intake" ".regulations/library" ".regulations/quarantine" ".regulations/work" else mkdir -p "data/${target}/workspaces" fi } # 确保 Docker 共享网络已创建 ensure_shared_network() { if ! docker network ls --format '{{.Name}}' | grep -q '^chat-agent-shared-net$'; then echo "[网络] 创建共享基础设施 Docker 网络: chat-agent-shared-net..." docker network create chat-agent-shared-net >/dev/null 2>&1 || true fi } run_compose() { local target="$1" local act="$2" local compose_file="docker-compose.${target}.yml" local env_file="configs/.env.${target}" if [[ ! -f "${compose_file}" ]]; then echo "[错误] 找不到编排文件: ${compose_file}" exit 1 fi # status/ps 为只读查询,不隐式创建配置或数据目录 if [[ "${act}" != "status" && "${act}" != "ps" ]]; then prepare_env_file "${target}" fi local env_args=() # 根 .env 提供跨环境共用的模型、检索与认证配置;环境文件仅覆盖端口、 # 数据库和其他环境差异。后声明的 env-file 优先级更高。 if [[ -f ".env" ]]; then env_args+=(--env-file ".env") fi if [[ -f "${env_file}" ]]; then env_args+=(--env-file "${env_file}") fi # Wave 2C: optional pinned image digests from candidate-images-apply.sh local images_file="configs/.env.${target}.images" local pinned=0 if [[ -f "${images_file}" ]]; then env_args+=(--env-file "${images_file}") if grep -q '^PINNED_IMAGE_MODE=1' "${images_file}" 2>/dev/null; then pinned=1 fi fi case "${act}" in start) ensure_shared_network echo "==> [${target}] 启动服务 (${compose_file})..." if [[ "${pinned}" -eq 1 && ( "${target}" == "test" || "${target}" == "main" ) ]]; then echo "==> [${target}] pinned image mode: up -d --no-build" docker compose -f "${compose_file}" "${env_args[@]}" up -d --no-build else docker compose -f "${compose_file}" "${env_args[@]}" up -d fi echo "==> [${target}] 服务已启动" ;; stop) echo "==> [${target}] 停止服务..." docker compose -f "${compose_file}" "${env_args[@]}" stop ;; restart) ensure_shared_network echo "==> [${target}] 重启现有容器(不会重建镜像)..." docker compose -f "${compose_file}" "${env_args[@]}" restart ;; rebuild) ensure_shared_network if [[ "${pinned}" -eq 1 && ( "${target}" == "test" || "${target}" == "main" ) ]]; then echo "==> [${target}] pinned image mode: recreate from pinned digests (refusing docker build)" docker compose -f "${compose_file}" "${env_args[@]}" up -d --no-build --force-recreate echo "==> [${target}] 已按 pin 重建容器(未重新 build 镜像)" else echo "==> [${target}] 重建镜像并重建容器..." docker compose -f "${compose_file}" "${env_args[@]}" up -d --build echo "==> [${target}] 镜像与容器已更新" fi ;; status|ps) echo "------------------------------------------------------------" echo "环境: [${target}] (编排: ${compose_file})" if [[ "${pinned}" -eq 1 ]]; then echo "image pin: ${images_file}" fi echo "------------------------------------------------------------" docker compose -f "${compose_file}" "${env_args[@]}" ps echo "" ;; logs) docker compose -f "${compose_file}" "${env_args[@]}" logs -f --tail=100 ;; down) echo "==> [${target}] 清理并下线容器..." docker compose -f "${compose_file}" "${env_args[@]}" down ;; *) echo "[错误] 未知操作: ${act}" show_usage ;; esac } case "${ENV_TARGET}" in shared|dev|test|main) run_compose "${ENV_TARGET}" "${ACTION}" ;; all) case "${ACTION}" in start) run_compose "shared" "start" run_compose "dev" "start" run_compose "test" "start" run_compose "main" "start" ;; stop|down) run_compose "dev" "${ACTION}" run_compose "test" "${ACTION}" run_compose "main" "${ACTION}" run_compose "shared" "${ACTION}" ;; status|ps) run_compose "shared" "status" run_compose "dev" "status" run_compose "test" "status" run_compose "main" "status" ;; rebuild) run_compose "shared" "rebuild" run_compose "dev" "rebuild" run_compose "test" "rebuild" run_compose "main" "rebuild" ;; *) echo "[错误] 'all' 仅支持 start | stop | down | rebuild | status 操作" exit 1 ;; esac ;; *) echo "[错误] 未知环境目标: ${ENV_TARGET}" show_usage ;; esac