diff --git a/.task/feature-duplication-lesson-records.md b/.task/feature-duplication-lesson-records.md index 46dd1512..1f38fdcc 100644 --- a/.task/feature-duplication-lesson-records.md +++ b/.task/feature-duplication-lesson-records.md @@ -35,3 +35,4 @@ | 同步前验收 | fcddfee5be45870071c18d921173f58bfab9e851 | f0e7831ed1cddc67621f873c2e49cba02f81fc3c | 2026-09-30T11:38:44Z | | 同步前验收 | 1174431ee6953ddc6e319f8fdbd8ecbf18576dbf | e3ba86aa16ceaa052bd5f9c2568e9a6c9cdd90d0 | 2026-09-30T11:50:25Z | | 同步前验收 | f832f0413e0baa358e7422db5edacb180188635a | 5c2fa4562b3010a94bbaa4731299223e70953ee4 | 2026-09-30T11:56:42Z | +| 同步前验收 | 251bd94847595dcb01c5571448b28ee3d75da956 | 52d0e45c81edd4e8ea2226439766301610d8faa3 | 2026-09-30T12:00:26Z | diff --git a/.task/feature-svc-envfile.md b/.task/feature-svc-envfile.md new file mode 100644 index 00000000..aac7aa1e --- /dev/null +++ b/.task/feature-svc-envfile.md @@ -0,0 +1,20 @@ +# feature/svc-envfile 任务卡 + +任务主记录:;状态:已完成;owner:weichen + +## 基线与授权 + +- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0 = 251bd94847595dcb01c5571448b28ee3d75da956 +- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-svc-envfile;分支:feature/svc-envfile +- 授权与交付终点:用户要求 --env 指定环境配置文件(如 ./.env.dev)且 --port 等 CLI 参数可覆盖文件配置 → 实现并实测后自主闭环合入。 + +## 目标与非目标 + +- 目标:--env FILE(任意位置,支持 = 形式);dotenv 安全解析(# 注释、成对引号、export 前缀、仅合法变量名、不执行文件内容);优先级 CLI > --env > .svc.env > 系统环境变量 > 默认;与播种机制贯通。 +- 非目标:不做多文件合并语法;不引入真实 dotenv 依赖。 + +## 结果与验证 + +- 结果:svc.sh 预扫 --env + load_env_file 安全解析 + help 更新。 +- 验证(全实测):.env.dev(含 export/引号/注释)地址与模型生效并播种(ss 验证 *:8830);--port 8831 覆盖文件端口、host 仍取文件(*:8831);缺文件快速报错;.svc.env 与 --env 分层(重叠键 --env 赢、非重叠键合并,tavily 保留)。 +- 影响与剩余项:.env.* 文件含密钥需自行 gitignore(.svc.env 已内置,示例文件已在 help 标注建议)。 diff --git a/scripts/svc.sh b/scripts/svc.sh index 862a08cd..4995f5fc 100755 --- a/scripts/svc.sh +++ b/scripts/svc.sh @@ -6,7 +6,10 @@ # action:deploy(默认)| stop | start | restart | status | logs [行数] # scripts/svc.sh status # 全环境总览 # -# 参数(优先级:命令行 > 环境变量 > 默认值): +# 参数(优先级:命令行 > --env 配置文件 > 环境变量/.svc.env > 默认值): +# --env FILE 环境配置文件(dotenv:KEY=VALUE,支持 # 注释、成对引号、 +# export 前缀;只识别合法变量名,不执行文件内容)。 +# 文件里写 SVC_DEV_ADDR/SVC_MODEL_* 等,CLI 参数可覆盖其中任意项 # --addr host:port 监听地址(整体覆盖) # --host host 只改主机部分(如 0.0.0.0 对外开放) # --port port 只改端口部分 @@ -28,14 +31,18 @@ # dev: 127.0.0.1:8788 数据 runtime/dev/data;SVC_DEV_ADDR/DATA/MOCK=1 可覆盖 # prod: 127.0.0.1:8787 数据 runtime/prod/data;SVC_PROD_ADDR/DATA/DSN/AUTH 可覆盖 # -# 一次性配置:把 SVC_MODEL_BASE/SVC_MODEL_KEY/SVC_MODEL_NAME 等写入 /.svc.env -#(已 gitignore),此后每次 start/deploy 自动播种,服务起来即可用。 +# 一次性配置:.svc.env(自动加载)或 --env 指定文件(如 .env.dev/.env.prod, +# 均建议 gitignore)。文件示例(.env.dev): +# SVC_DEV_ADDR=0.0.0.0:8790 +# SVC_MODEL_BASE=https://api.xxx.com/v1 +# SVC_MODEL_KEY=sk-xx +# SVC_MODEL_NAME=glm-4.7 # # 示例: -# scripts/svc.sh dev --port 9000 --mock +# scripts/svc.sh dev --env ./.env.dev # 用配置文件启动 +# scripts/svc.sh dev --env ./.env.dev --port 9000 # CLI 覆盖文件里的端口 # scripts/svc.sh prod --host 0.0.0.0 --dsn postgres://u:p@127.0.0.1:5432/as -# scripts/svc.sh dev --model-base https://api.xxx.com/v1 --model-key sk-xx --model-name glm-4.7 -# make svc ENV=dev ARGS="--port 9000" +# make svc ENV=dev ARGS="--env .env.dev --port 9000" # # 安全约束: # - 只停自己启动过的进程(pidfile + /proc//cmdline 双重校验),绝不动他人现场; @@ -46,9 +53,6 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" RUNTIME="$ROOT/runtime" BIN="$ROOT/bin/agent-system" -# 一次性配置文件(含密钥,已 gitignore):SVC_MODEL_* 等在此设好后每次启动自动生效。 -# shellcheck disable=SC1090 -[ -f "$ROOT/.svc.env" ] && . "$ROOT/.svc.env" c_info() { printf '\033[1;34m[svc]\033[0m %s\n' "$*"; } c_ok() { printf '\033[1;32m[svc]\033[0m %s\n' "$*"; } @@ -56,7 +60,27 @@ c_warn() { printf '\033[1;33m[svc]\033[0m %s\n' "$*"; } c_err() { printf '\033[1;31m[svc]\033[0m %s\n' "$*" >&2; } usage() { - sed -n '2,36p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' + sed -n '2,46p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' +} + +# load_env_file FILE:dotenv 安全解析——逐行 KEY=VALUE 赋值(剥 export 前缀与成对 +# 引号,忽略注释/空行/非法行),不 source 执行文件内容。 +load_env_file() { + local f="$1" line k v + [ -f "$f" ] || { c_err "环境配置文件不存在:$f"; exit 2; } + while IFS= read -r line || [ -n "$line" ]; do + line="${line%$'\r'}" + case "$line" in ''|'#'*) continue ;; esac + [[ "$line" =~ ^[[:space:]]*(export[[:space:]]+)?[A-Za-z_][A-Za-z0-9_]*= ]] || continue + k="${line%%=*}" + k="${k#"${k%%[![:space:]]*}"}" # 左去空白 + k="${k#export}" + k="${k#"${k%%[![:space:]]*}"}" + v="${line#*=}" + v="${v#"${v%%[![:space:]]*}"}" # 值左去空白 + v="${v%\"}"; v="${v%\'}"; v="${v#\"}"; v="${v#\'}" # 剥成对引号 + printf -v "$k" '%s' "$v" + done < "$f" } # ---- 命令行参数(覆盖层)---- @@ -280,6 +304,20 @@ env_status_line() { main() { [ $# -ge 1 ] || { usage; exit 2; } + # 预扫 --env(支持 --env FILE 与 --env=FILE,可出现在任意位置): + # 配置层优先级 = CLI 参数 > --env 文件 > .svc.env > 系统环境变量 > 默认值。 + local svc_envfile="" a + local pre=() + while [ $# -gt 0 ]; do + case "$1" in + --env) [ $# -ge 2 ] || { c_err "--env 需要文件路径"; exit 2; }; svc_envfile="$2"; shift 2 ;; + --env=*) svc_envfile="${1#--env=}"; shift ;; + *) pre+=("$1"); shift ;; + esac + done + set -- ${pre[@]+"${pre[@]}"} + [ -f "$ROOT/.svc.env" ] && load_env_file "$ROOT/.svc.env" + [ -n "$svc_envfile" ] && load_env_file "$svc_envfile" if [ "$1" = "status" ] && [ $# -eq 1 ]; then c_info "服务总览(root: $ROOT)" for e in dev prod; do ENV_NAME="$e"; env_config "$e"; env_status_line; done