U10a 尾片:config check 子命令(加载校验+脱敏摘要,零副作用) #11

Merged
weichen merged 1 commits from feature/u10a-config-check-2026-09-29 into dev 2026-09-29 07:30:47 +00:00
2 changed files with 91 additions and 0 deletions
+53
View File
@@ -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: 通过")
}
+38
View File
@@ -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)
}