diff --git a/Makefile b/Makefile index 5776a98b..d0c8b0ad 100644 --- a/Makefile +++ b/Makefile @@ -94,8 +94,8 @@ test: test-core test-backend test-frontend test-ui test-agentcmd # 运行演示(mock 模式):单二进制 + 示例插件 + 嵌入式存储,浏览器 http://127.0.0.1:8787 # 环境服务管理(scripts/svc.sh):deploy = 停旧 → 构建 → 启动;dev/prod 相互隔离。 svc: - @test -n "$(ENV)" || (echo "用法:make svc ENV=dev|prod [ACTION=deploy|stop|start|restart|status|logs]"; exit 2) - scripts/svc.sh $(ENV) $(ACTION) + @test -n "$(ENV)" || (echo "用法:make svc ENV=dev|prod [ACTION=deploy|stop|start|restart|status|logs] [ARGS='--port 9000 --mock ...']"; exit 2) + scripts/svc.sh $(ENV) $(ACTION) $(ARGS) demo: build $(BIN) serve --addr 127.0.0.1:8787 --data ./data --plugins $(PLUGINS) --mock diff --git a/scripts/svc.sh b/scripts/svc.sh index 8ec59f60..ccad2e05 100755 --- a/scripts/svc.sh +++ b/scripts/svc.sh @@ -2,17 +2,32 @@ # svc.sh —— 多环境服务管理:按环境隔离地 停止旧服务 → 构建最新 → 启动。 # # 用法: -# scripts/svc.sh dev # 部署 dev(停止旧 dev → make build → 启动) -# scripts/svc.sh prod # 部署 prod(dev 不受影响;反之亦然) -# scripts/svc.sh dev stop|start|restart|status|logs -# scripts/svc.sh status # 全环境总览 +# scripts/svc.sh [action] [参数...] +# action:deploy(默认)| stop | start | restart | status | logs [行数] +# scripts/svc.sh status # 全环境总览 # -# 环境隔离(默认值,可用环境变量覆盖): -# dev: 127.0.0.1:8788 数据 runtime/dev/data 日志 runtime/dev/svc.log -# prod: 127.0.0.1:8787 数据 runtime/prod/data 日志 runtime/prod/svc.log -# 覆盖:SVC_DEV_ADDR / SVC_PROD_ADDR、SVC_DEV_DATA / SVC_PROD_DATA、 -# SVC_DEV_MOCK=1(dev 加 --mock)、SVC_PROD_DSN(PG 档)、 -# SVC_PROD_AUTH(on|off|auto,默认 auto)。 +# 参数(优先级:命令行 > 环境变量 > 默认值): +# --addr host:port 监听地址(整体覆盖) +# --host host 只改主机部分(如 0.0.0.0 对外开放) +# --port port 只改端口部分 +# --data dir 数据目录(SQLite 与工件) +# --dsn DSN PostgreSQL 档 DSN(数据库地址/端口/库名都在 DSN 内,如 +# postgres://user:pass@dbhost:5432/agentdb)——含密码时建议 +# 改用环境变量 SVC_PROD_DSN 传递,避免进 shell 历史 +# --auth on|off|auto API key 认证(默认 auto:PG 档开、嵌入式关) +# --mock 演示模式(dev 常用) +# --plugins dir 插件目录(默认 /plugins) +# -- <剩余参数> 原样透传给 agent-system serve(escape hatch) +# +# 环境变量默认值: +# 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 可覆盖 +# +# 示例: +# scripts/svc.sh dev --port 9000 --mock +# scripts/svc.sh prod --host 0.0.0.0 --dsn postgres://u:p@127.0.0.1:5432/as +# scripts/svc.sh dev deploy --addr 0.0.0.0:8788 +# make svc ENV=dev ARGS="--port 9000" # # 安全约束: # - 只停自己启动过的进程(pidfile + /proc//cmdline 双重校验),绝不动他人现场; @@ -28,31 +43,73 @@ c_ok() { printf '\033[1;32m[svc]\033[0m %s\n' "$*"; } 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\}//' +} + +# ---- 命令行参数(覆盖层)---- +CLI_ADDR=""; CLI_HOST=""; CLI_PORT=""; CLI_DATA=""; CLI_DSN="" +CLI_AUTH=""; CLI_MOCK=0; CLI_PLUGINS=""; CLI_EXTRA=""; CLI_LOGS_N="" + +need_value() { [ $# -ge 2 ] || { c_err "参数 $1 需要值"; exit 2; }; } + +parse_args() { + while [ $# -gt 0 ]; do + case "$1" in + --addr) need_value "$@"; CLI_ADDR="$2"; shift 2 ;; + --host) need_value "$@"; CLI_HOST="$2"; shift 2 ;; + --port) need_value "$@"; CLI_PORT="$2"; shift 2 ;; + --data) need_value "$@"; CLI_DATA="$2"; shift 2 ;; + --dsn) need_value "$@"; CLI_DSN="$2"; shift 2 ;; + --auth) need_value "$@"; CLI_AUTH="$2"; shift 2 ;; + --plugins) need_value "$@"; CLI_PLUGINS="$2"; shift 2 ;; + --mock) CLI_MOCK=1; shift ;; + --) shift; CLI_EXTRA="$*"; break ;; + -h|--help) usage; exit 0 ;; + *) c_err "未知参数:$1"; usage; exit 2 ;; + esac + done +} + +# addr_compose 基础值(host:port)按 CLI 的 --addr/--host/--port 组合出最终地址。 +addr_compose() { + local base="$1" host port + host="${base%:*}"; port="${base##*:}" + [ -n "$CLI_ADDR" ] && { echo "$CLI_ADDR"; return 0; } + [ -n "$CLI_HOST" ] && host="$CLI_HOST" + [ -n "$CLI_PORT" ] && port="$CLI_PORT" + echo "$host:$port" +} + env_config() { case "$1" in dev) - ENV_ADDR="${SVC_DEV_ADDR:-127.0.0.1:8788}" - ENV_DATA="${SVC_DEV_DATA:-$RUNTIME/dev/data}" + ENV_ADDR="$(addr_compose "${SVC_DEV_ADDR:-127.0.0.1:8788}")" + ENV_DATA="${CLI_DATA:-${SVC_DEV_DATA:-$RUNTIME/dev/data}}" ENV_DIR="$RUNTIME/dev" - ENV_ARGS="" - [ "${SVC_DEV_MOCK:-0}" = "1" ] && ENV_ARGS="--mock" ;; prod) - ENV_ADDR="${SVC_PROD_ADDR:-127.0.0.1:8787}" - ENV_DATA="${SVC_PROD_DATA:-$RUNTIME/prod/data}" + ENV_ADDR="$(addr_compose "${SVC_PROD_ADDR:-127.0.0.1:8787}")" + ENV_DATA="${CLI_DATA:-${SVC_PROD_DATA:-$RUNTIME/prod/data}}" ENV_DIR="$RUNTIME/prod" - ENV_ARGS="" - [ -n "${SVC_PROD_DSN:-}" ] && ENV_ARGS="$ENV_ARGS --dsn $SVC_PROD_DSN" - ENV_ARGS="$ENV_ARGS --auth ${SVC_PROD_AUTH:-auto}" ;; *) c_err "未知环境:$1(可用:dev | prod)"; usage; exit 2 ;; esac ENV_PID="$ENV_DIR/svc.pid" ENV_LOG="$ENV_DIR/svc.log" -} + ENV_PLUGINS="${CLI_PLUGINS:-$ROOT/plugins}" -usage() { - sed -n '2,20p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' + # 运行参数:mock/DSN/auth(CLI 与环境变量取并集,CLI 优先)。 + ENV_ARGS="" + if [ "$ENV_NAME" = dev ]; then + if [ "${SVC_DEV_MOCK:-0}" = "1" ] || [ "$CLI_MOCK" = 1 ]; then + ENV_ARGS="--mock" + fi + else + local dsn="${CLI_DSN:-${SVC_PROD_DSN:-}}" + if [ -n "$dsn" ]; then ENV_ARGS="$ENV_ARGS --dsn $dsn"; fi + ENV_ARGS="$ENV_ARGS --auth ${CLI_AUTH:-${SVC_PROD_AUTH:-auto}}" + fi } # pid_alive :进程存在且 cmdline 是我们的 serve(防 PID 复用误杀)。 @@ -60,7 +117,7 @@ pid_alive() { local pid="$1" [ -n "$pid" ] && [ -r "/proc/$pid/cmdline" ] || return 1 local cmd - cmd="$(tr '\0' ' ' < "/proc/$pid/cmdline")" || return 1 + cmd="$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null)" || return 1 case "$cmd" in *agent-system*serve*) return 0 ;; *) return 1 ;; @@ -109,7 +166,7 @@ do_build() { do_start() { if pid_alive "$(read_pid)"; then - c_warn "[$ENV_NAME] 已在运行 pid=$(read_pid)(如需换新版本请用 deploy)" + c_warn "[$ENV_NAME] 已在运行 pid=$(read_pid)(如需换参数/新版本请用 deploy/restart)" return 0 fi mkdir -p "$ENV_DATA" "$ENV_DIR" @@ -117,21 +174,22 @@ do_start() { local owner; owner="$(port_owner)" if [ -n "$owner" ]; then c_err "[$ENV_NAME] 端口被占用(非本脚本管理):$owner" - c_err "处理:停掉该进程,或用环境变量换端口(如 SVC_${ENV_NAME^^}_ADDR=127.0.0.1:8799)" + c_err "处理:停掉该进程,或换端口(--port 8799 / --addr host:port)" exit 1 fi [ -x "$BIN" ] || { c_err "[$ENV_NAME] 缺少二进制 $BIN——先构建(deploy 会自动构建)"; exit 1; } - c_info "[$ENV_NAME] 启动:$BIN serve --addr $ENV_ADDR --data $ENV_DATA --plugins $ROOT/plugins $ENV_ARGS" + c_info "[$ENV_NAME] 启动:$BIN serve --addr $ENV_ADDR --data $ENV_DATA --plugins $ENV_PLUGINS $ENV_ARGS $CLI_EXTRA" # 直接 nohup 简单命令(不经 cd 复合后台作业):$! 即服务进程本身,pidfile 语义才成立。 - nohup "$BIN" serve --addr "$ENV_ADDR" --data "$ENV_DATA" --plugins "$ROOT/plugins" $ENV_ARGS \ + nohup "$BIN" serve --addr "$ENV_ADDR" --data "$ENV_DATA" --plugins "$ENV_PLUGINS" $ENV_ARGS $CLI_EXTRA \ >> "$ENV_LOG" 2>&1 & echo $! > "$ENV_PID" printf 'addr=%s\ndata=%s\n' "$ENV_ADDR" "$ENV_DATA" > "$ENV_DIR/svc.env" - # 健康等待:最多 20s。 - local port="${ENV_ADDR##*:}" + # 健康等待:最多 20s;探测主机取绑定主机(0.0.0.0/:: → 127.0.0.1)。 + local host="${ENV_ADDR%:*}" port="${ENV_ADDR##*:}" + if [ "$host" = "0.0.0.0" ] || [ "$host" = "::" ]; then host="127.0.0.1"; fi for _ in $(seq 1 100); do - if curl -s -o /dev/null -m 2 "http://127.0.0.1:$port/" 2>/dev/null; then - c_ok "[$ENV_NAME] 已启动 pid=$(cat "$ENV_PID") → http://127.0.0.1:$port(日志:$ENV_LOG)" + if curl -s -o /dev/null -m 2 "http://$host:$port/" 2>/dev/null; then + c_ok "[$ENV_NAME] 已启动 pid=$(cat "$ENV_PID") → http://$host:$port(日志:$ENV_LOG)" return 0 fi pid_alive "$(read_pid)" || { c_err "[$ENV_NAME] 进程启动后即退出,查看日志:tail -40 $ENV_LOG"; exit 1; } @@ -160,19 +218,28 @@ main() { [ $# -ge 1 ] || { usage; exit 2; } if [ "$1" = "status" ] && [ $# -eq 1 ]; then c_info "服务总览(root: $ROOT)" - for e in dev prod; do env_config "$e"; ENV_NAME="$e"; env_status_line; done + for e in dev prod; do ENV_NAME="$e"; env_config "$e"; env_status_line; done exit 0 fi - ENV_NAME="$1"; env_config "$ENV_NAME" - local action="${2:-deploy}" + ENV_NAME="$1"; shift + # 动作词可省略(省略 = deploy);flags 可直接跟随环境名。 + local action="deploy" + case "${1:-}" in + deploy|stop|start|restart|status|logs) action="$1"; shift ;; + esac + # logs 的行数(兼容 svc.sh dev logs 100)。 + if [ "$action" = "logs" ] && [ "${1:-}" != "" ] && [[ "$1" =~ ^[0-9]+$ ]]; then + CLI_LOGS_N="$1"; shift + fi + parse_args "$@" + env_config "$ENV_NAME" case "$action" in deploy) do_stop; do_build; do_start ;; stop) do_stop ;; start) do_start ;; restart) do_stop; do_start ;; status) env_status_line ;; - logs) [ -f "$ENV_LOG" ] && tail -n "${3:-50}" -f "$ENV_LOG" || c_warn "[$ENV_NAME] 暂无日志" ;; - *) c_err "未知动作:$action(可用:deploy|stop|start|restart|status|logs)"; usage; exit 2 ;; + logs) [ -f "$ENV_LOG" ] && tail -n "${CLI_LOGS_N:-50}" -f "$ENV_LOG" || c_warn "[$ENV_NAME] 暂无日志" ;; esac }