@@ -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 < dev|prod> [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 插件目录(默认 <repo>/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/<pid>/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 <pid>:进程存在且 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
}