Files
weichen fed87ad14b feat(governance): enforce managed worktrees and scoped agent operations
Consolidate approved policies and add local lease, document CAS, commit, candidate and environment gates. Preserve conservative recovery and cleanup limits.
2026-09-21 14:41:40 +00:00

225 lines
8.0 KiB
Bash
Executable File

#!/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 | ps | logs | down
# =============================================================================
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
ENV_TARGET="${1:-}"
ACTION="${2:-}"
show_usage() {
cat <<EOF
用法:
$0 <shared|dev|test|main|all> <start|stop|restart|rebuild|status|ps|logs|down>
示例:
$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
# Reject invalid requests and unauthorized writes before configuration, storage,
# network or Docker operations. The guard verifies the live supervisor lease.
case "${ENV_TARGET}" in
shared|dev|test|main|all) ;;
*) show_usage ;;
esac
case "${ACTION}" in
status|ps|logs) ;;
start|stop|restart|rebuild|down)
python3 "${SCRIPT_DIR}/agentctl.py" guard env --target "${ENV_TARGET}"
python3 "${SCRIPT_DIR}/agentctl.py" --repo "${ROOT_DIR}" guard env --target "${ENV_TARGET}"
;;
*) show_usage ;;
esac
cd "${ROOT_DIR}"
# 确保环境变量配置文件与存储目录存在
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/logs 为只读查询,不隐式创建配置或数据目录
if [[ "${act}" != "status" && "${act}" != "ps" && "${act}" != "logs" ]]; 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