diff --git a/cmd/xgift/main.go b/cmd/xgift/main.go index cf00106..c71d707 100644 --- a/cmd/xgift/main.go +++ b/cmd/xgift/main.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "context" "encoding/json" "errors" @@ -71,7 +72,7 @@ func run() error { pay := f.Bool("pay", false, "pay only at the exact allowed BDT total") name := f.String("name", "", "secret name for put") f.Usage = func() { - fmt.Fprintln(f.Output(), "Usage: xgift [flags]\nsetup is the interactive first-time wizard; init reads a JSON object from stdin; put reads one JSON value from stdin.") + fmt.Fprintln(f.Output(), "Usage: xgift [flags]\nsetup is the interactive first-time wizard; init reads a JSON object from stdin; put reads one JSON value from stdin (stripe-key: the raw pk_live_ key).") f.PrintDefaults() } if err := f.Parse(rest); err != nil { @@ -191,22 +192,22 @@ func run() error { } raw, e := v.Get("api-auth") if e != nil { - return errors.New("record api-auth is missing; run setup") + return errors.New("record api-auth is missing; fix with put --name api-auth") } var auth struct{ Authorization string } if e = json.Unmarshal(raw, &auth); e != nil || !strings.HasPrefix(auth.Authorization, "Bearer ") { clear(raw) - return errors.New("invalid api-auth record; run setup to rewrite it") + return errors.New("invalid api-auth record; rewrite with put --name api-auth") } clear(raw) fmt.Println("api-auth: encrypted record verified") key, e := v.Get("stripe-key") if e != nil { - return errors.New("record stripe-key is missing; run setup") + return errors.New("record stripe-key is missing; fix with put --name stripe-key") } if !stripeKeyPattern.Match(key) { clear(key) - return errors.New("invalid stripe-key record; run setup to rewrite it") + return errors.New("invalid stripe-key record; rewrite with put --name stripe-key") } clear(key) fmt.Println("stripe-key: encrypted record verified") @@ -223,18 +224,31 @@ func run() error { fmt.Println("X cookies refreshed in encrypted SQLite vault") return nil case "put": - if *name != "proxy" && *name != "card" && *name != "cookies" { - return errors.New("--name must be proxy, card or cookies") + switch *name { + case "proxy", "card", "cookies", "api-auth": + b, e := io.ReadAll(io.LimitReader(os.Stdin, 1024*1024)) + if e != nil { + return e + } + defer clear(b) + if !json.Valid(b) { + return errors.New("stdin must be valid JSON") + } + return v.Put(*name, b) + case "stripe-key": + b, e := io.ReadAll(io.LimitReader(os.Stdin, 4096)) + if e != nil { + return e + } + defer clear(b) + b = bytes.TrimSpace(b) + if !stripeKeyPattern.Match(b) { + return errors.New("stdin must be the pk_live_ publishable key") + } + return v.Put(*name, b) + default: + return errors.New("--name must be proxy, card, cookies, api-auth or stripe-key") } - b, e := io.ReadAll(io.LimitReader(os.Stdin, 1024*1024)) - if e != nil { - return e - } - defer clear(b) - if !json.Valid(b) { - return errors.New("stdin must be valid JSON") - } - return v.Put(*name, b) } if command != "proxy" && command != "check" && !regexp.MustCompile(`^@?[A-Za-z0-9_]{1,15}$`).MatchString(command) { return errors.New("invalid command or X username") diff --git a/cmd/xgift/setup.go b/cmd/xgift/setup.go index 5c89f7c..4af5229 100644 --- a/cmd/xgift/setup.go +++ b/cmd/xgift/setup.go @@ -189,17 +189,22 @@ func runSetup(ctx context.Context, db, passwordFileFlag string) error { defer v.Close() fmt.Printf("加密保管库已创建:%s\n\n", db) - if err = w.setupXCredentials(v); err != nil { - return err + steps := []func() error{ + func() error { return w.setupXCredentials(v) }, + func() error { return w.setupCard(v) }, + func() error { return w.setupProxy(ctx, v) }, + func() error { return w.setupStripeKey(v) }, } - if err = w.setupCard(v); err != nil { - return err - } - if err = w.setupProxy(ctx, v); err != nil { - return err - } - if err = w.setupStripeKey(v); err != nil { - return err + for _, step := range steps { + if err = step(); err != nil { + fmt.Println() + fmt.Printf("配置未完成:%v\n", err) + fmt.Println("已创建的保管库会保留已写入的记录。修复方式:") + fmt.Printf(" 1) 删除 %s、%s 和旁边的 password-path 后重新运行 setup;或\n", db, passwordFile) + fmt.Println(" 2) 用 put --name / billing / import-chrome 补写缺失记录,") + fmt.Println(" 再以 status 确认全部记录就绪。") + return err + } } siteConfigured, err := w.setupSite(db, passwordFile) if err != nil { @@ -550,8 +555,8 @@ func (w *wizard) setupSite(db, passwordFile string) (bool, error) { return false, err } u, err := url.Parse(origin) - if err != nil || u.Scheme != "https" || u.Host == "" || (u.Path != "" && u.Path != "/") || u.RawQuery != "" || u.Fragment != "" { - return false, errors.New("Origin 必须是 https://<主机名> 形式") + if err != nil || u.Scheme != "https" || u.Host == "" || u.Path != "" || u.RawQuery != "" || u.Fragment != "" || u.User != nil { + return false, errors.New("Origin 必须是 https://<主机名> 形式(不带路径、查询或用户信息)") } listen, err := w.prompt("监听地址", "127.0.0.1:8787") if err != nil {