// Package config 解析各子命令的命令行开关。 package config import ( "flag" "fmt" "net" "strings" ) // Config 运行时配置,serve 与维护子命令共用。 type Config struct { Addr string DataDir string LogLevel string LogFormat string // text|json BehindProxy bool Dev bool AllowNewer bool } // DBPath SQLite 数据库文件路径。 func (c *Config) DBPath() string { return c.DataDir + "/pn.db" } func addCommonFlags(fs *flag.FlagSet, c *Config) { fs.StringVar(&c.DataDir, "data-dir", "./data", "数据目录(SQLite 数据库所在)") } func addServeFlags(fs *flag.FlagSet, c *Config) { addCommonFlags(fs, c) fs.StringVar(&c.Addr, "addr", ":8080", "HTTP 监听地址") fs.StringVar(&c.LogLevel, "log-level", "info", "日志级别(debug|info|warn|error)") fs.StringVar(&c.LogFormat, "log-format", "text", "日志格式(text|json)") fs.BoolVar(&c.BehindProxy, "behind-proxy", false, "位于可信反向代理之后(取 X-Forwarded-For 最右条目作为客户端 IP)") fs.BoolVar(&c.Dev, "dev", false, "开发模式:允许非 Secure Cookie,仅允许监听 loopback 地址") fs.BoolVar(&c.AllowNewer, "allow-newer", false, "允许在更新的数据库 schema 版本上运行(跳过版本上界守卫)") } // ParseStart 解析 `pn start` 参数。 func ParseStart(args []string) (*Config, error) { c := &Config{} fs := flag.NewFlagSet("start", flag.ContinueOnError) addServeFlags(fs, c) if err := fs.Parse(args); err != nil { return nil, err } if c.Dev && !isLoopbackAddr(c.Addr) { return nil, fmt.Errorf("--dev 仅允许监听 loopback 地址(如 127.0.0.1:8080),当前为 %q", c.Addr) } return c, nil } // ParseInit 解析 `pn init` 参数。 func ParseInit(args []string) (*Config, error) { c := &Config{} fs := flag.NewFlagSet("init", flag.ContinueOnError) addCommonFlags(fs, c) fs.BoolVar(&c.AllowNewer, "allow-newer", false, "允许在更新的数据库 schema 版本上运行(跳过版本上界守卫)") if err := fs.Parse(args); err != nil { return nil, err } return c, nil } // ParseMaint 解析 `pn backup` / `pn gc` / `pn passwd` 参数(仅数据目录; // 维护命令经 store.OpenData 打开,不迁移、无版本守卫,故无 --allow-newer,D6)。 // 返回 flag 解析后的剩余位置参数(如 backup 的输出路径)。 func ParseMaint(cmd string, args []string) (*Config, []string, error) { c := &Config{} fs := flag.NewFlagSet(cmd, flag.ContinueOnError) addCommonFlags(fs, c) if err := fs.Parse(args); err != nil { return nil, nil, err } return c, fs.Args(), nil } func isLoopbackAddr(addr string) bool { host, _, err := net.SplitHostPort(addr) if err != nil { // 无端口:整体视为主机名 host = addr } if host == "" || strings.EqualFold(host, "localhost") { return host != "" } ip := net.ParseIP(host) return ip != nil && ip.IsLoopback() }