RunCLI parses CLI args and executes enrollment. args excludes program and subcommand name (e.g. for `pulse-node enroll --server=... ...`, pass os.Args[2:]).
(args []string)
| 17 | // subcommand name (e.g. for `pulse-node enroll --server=... ...`, pass |
| 18 | // os.Args[2:]). |
| 19 | func RunCLI(args []string) error { |
| 20 | fs := flag.NewFlagSet("enroll", flag.ContinueOnError) |
| 21 | fs.SetOutput(os.Stderr) |
| 22 | fs.Usage = func() { |
| 23 | fmt.Fprintln(os.Stderr, "用法: pulse-node enroll --server=<URL> --node-id=<ID> --token=<TOKEN> [选项]") |
| 24 | fmt.Fprintln(os.Stderr, "") |
| 25 | fmt.Fprintln(os.Stderr, "选项:") |
| 26 | fs.PrintDefaults() |
| 27 | } |
| 28 | |
| 29 | var ( |
| 30 | serverURL = fs.String("server", "", "控制面 URL,例如 https://controlplane.example.com:8080") |
| 31 | nodeID = fs.String("node-id", "", "节点 ID") |
| 32 | token = fs.String("token", "", "一次性 enroll token") |
| 33 | tokenFile = fs.String("token-file", "", "从文件读取 token,'-' 表示 stdin") |
| 34 | outDir = fs.String("out", ".", "输出目录,写入 node_cert.pem / node_key.pem / node_ca.pem") |
| 35 | envOut = fs.String("env-out", "", "将 enroll 结果写入该 env 文件(追加/覆盖 PULSE_NODE_* 变量)") |
| 36 | insecure = fs.Bool("insecure", false, "跳过控制面 TLS 校验(首次 enroll 必需)") |
| 37 | fingerprint = fs.String("server-fingerprint", "", "控制面证书 SHA256 指纹(暂未实现,与 --insecure 互斥)") |
| 38 | ) |
| 39 | |
| 40 | if err := fs.Parse(args); err != nil { |
| 41 | return err |
| 42 | } |
| 43 | |
| 44 | if *serverURL == "" { |
| 45 | return errors.New("--server is required") |
| 46 | } |
| 47 | if *nodeID == "" { |
| 48 | return errors.New("--node-id is required") |
| 49 | } |
| 50 | if *outDir == "" { |
| 51 | return errors.New("--out cannot be empty") |
| 52 | } |
| 53 | |
| 54 | if *token != "" && *tokenFile != "" { |
| 55 | return errors.New("--token and --token-file are mutually exclusive") |
| 56 | } |
| 57 | if *token == "" && *tokenFile == "" { |
| 58 | return errors.New("either --token or --token-file is required") |
| 59 | } |
| 60 | |
| 61 | if *insecure && *fingerprint != "" { |
| 62 | return errors.New("--insecure and --server-fingerprint are mutually exclusive") |
| 63 | } |
| 64 | if *fingerprint != "" { |
| 65 | // TODO: implement fingerprint pinning. |
| 66 | return errors.New("--server-fingerprint is not yet implemented; use --insecure for now") |
| 67 | } |
| 68 | if !*insecure { |
| 69 | return errors.New("--insecure must be set explicitly for first-time enrollment (fingerprint pinning is not yet implemented)") |
| 70 | } |
| 71 | |
| 72 | resolvedToken := *token |
| 73 | if *tokenFile != "" { |
| 74 | t, err := readToken(*tokenFile) |
| 75 | if err != nil { |
| 76 | return fmt.Errorf("read token: %w", err) |