feat(cli): 'config check' subcommand (U10a tail, KTD16/17 minimal)
- server config check: load + validate + masked summary; exit 1 on issues - no side effects: creates no DB, starts nothing, writes no business dirs - Config.Check() (production secrets, port range, data dir) exported for reuse; MaskedSummary never prints credential values - default no-arg invocation unchanged (serve); help added - smoke-verified: pass exit 0, invalid port exit 1 with issue line - layer-source attribution (config explain) deferred to U10b ConfigSnapshot
This commit is contained in:
@@ -19,6 +19,22 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// KTD16:同一程序提供运维子命令;默认(无子命令)保持 serve 行为不变。
|
||||
if len(os.Args) > 1 {
|
||||
switch os.Args[1] {
|
||||
case "config":
|
||||
if len(os.Args) > 2 && os.Args[2] == "check" {
|
||||
configCheck(os.Args[3:])
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "用法: server config check [--env path] [--port N] [--host H] [--backend name]")
|
||||
os.Exit(2)
|
||||
case "-h", "--help", "help":
|
||||
fmt.Println("用法: server [serve flags] 启动服务(默认)\n server config check [flags] 加载并校验配置(脱敏摘要,无副作用)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
envPath := flag.String("env", ".env", "环境配置文件路径")
|
||||
port := flag.Int("port", 0, "HTTP 服务端口 (默认读取配置文件或 8080)")
|
||||
host := flag.String("host", "", "HTTP 监听主机 (默认读取配置文件或 127.0.0.1)")
|
||||
@@ -157,3 +173,40 @@ func shutdownDrainTimeout(cfg *config.Config) time.Duration {
|
||||
}
|
||||
return at + 30*time.Second
|
||||
}
|
||||
|
||||
|
||||
// configCheck 加载并校验配置:输出脱敏摘要与问题清单。
|
||||
// 不创建 DB、不启动服务、不写业务目录(KTD17 config check 约束)。
|
||||
func configCheck(args []string) {
|
||||
fs := flag.NewFlagSet("config check", flag.ExitOnError)
|
||||
envPath := fs.String("env", ".env", "环境配置文件路径")
|
||||
port := fs.Int("port", 0, "HTTP 端口覆盖")
|
||||
host := fs.String("host", "", "HTTP 主机覆盖")
|
||||
backend := fs.String("backend", "", "智能体后端覆盖")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
cfg, err := config.Load(*envPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "config check: 加载失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if *port > 0 {
|
||||
cfg.HTTPPort = *port
|
||||
}
|
||||
if *host != "" {
|
||||
cfg.HTTPHost = *host
|
||||
}
|
||||
if *backend != "" {
|
||||
cfg.AgentBackend = *backend
|
||||
}
|
||||
|
||||
fmt.Println(cfg.MaskedSummary())
|
||||
if issues := cfg.Check(); len(issues) > 0 {
|
||||
fmt.Fprintln(os.Stderr, "config check: 未通过")
|
||||
for _, it := range issues {
|
||||
fmt.Fprintf(os.Stderr, " - %s\n", it)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("config check: 通过")
|
||||
}
|
||||
|
||||
@@ -737,3 +737,41 @@ func RedactPostgresDSN(dsn string) string {
|
||||
func (c *Config) ContextDSN() string {
|
||||
return "postgres://" + c.ContextDbUser + ":" + c.ContextDbPassword + "@" + c.ContextDbHost + ":" + strconv.Itoa(c.ContextDbPort) + "/" + c.ContextDbName + "?sslmode=disable"
|
||||
}
|
||||
|
||||
|
||||
// Check 返回配置问题列表(空 = 通过)。只读、无副作用,供 config check
|
||||
// 子命令与部署预检使用;完整分层溯源(explain)随 U10b ConfigSnapshot 提供。
|
||||
func (c *Config) Check() []string {
|
||||
var issues []string
|
||||
if c == nil {
|
||||
return []string{"config is nil"}
|
||||
}
|
||||
if err := validateProductionSecrets(c); err != nil {
|
||||
issues = append(issues, err.Error())
|
||||
}
|
||||
if c.HTTPPort <= 0 || c.HTTPPort > 65535 {
|
||||
issues = append(issues, fmt.Sprintf("http port out of range: %d", c.HTTPPort))
|
||||
}
|
||||
if strings.TrimSpace(c.DataDir) == "" {
|
||||
issues = append(issues, "data dir is empty")
|
||||
}
|
||||
return issues
|
||||
}
|
||||
|
||||
// MaskedSummary 输出脱敏的关键配置摘要(不含任何凭证值)。
|
||||
func (c *Config) MaskedSummary() string {
|
||||
mask := func(s string) string {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return "<unset>"
|
||||
}
|
||||
return "<set>"
|
||||
}
|
||||
return fmt.Sprintf("env=%s data_dir=%s listen=%s:%d backend=%s timeout=%s\n"+
|
||||
"app_db=%s:%d/%s user=%s password=%s\n"+
|
||||
"kb_db=%s:%d/%s user=%s password=%s\n"+
|
||||
"runner_url=%s runner_token=%s agentx_key=%s allow_in_memory=%v",
|
||||
c.Env, c.DataDir, c.HTTPHost, c.HTTPPort, c.AgentBackend, c.AgentTimeout,
|
||||
c.ContextDbHost, c.ContextDbPort, c.ContextDbName, c.ContextDbUser, mask(c.ContextDbPassword),
|
||||
c.KbDbHost, c.KbDbPort, c.KbDbName, c.KbDbUser, mask(c.KbDbPassword),
|
||||
c.PiAgentRunnerURL, mask(c.PiAgentRunnerAuthToken), mask(c.AgentXAPIKey), c.AllowInMemory)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user