File size: 4,663 Bytes
6d60378
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package main

import (
	"flag"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/linguo2625469/workbuddy2api-panel/internal/syncer"
)

// usageHead 帮助文本前半段。
const usageHead = `wb2api-launcher —— WorkBuddy2API 独立启动器(同步凭证 + 拉起网关 + 后台对账)

用法:
  wb2api-launcher [标志]

常用:
  wb2api-launcher                     同步一次 → 启动网关 → 后台每 5 分钟对账
  wb2api-launcher -status             只看两边差异,不启动网关(有差异退出码 2)
  wb2api-launcher -sync-only          同步一次就退出
  wb2api-launcher -no-sync            离线启动(跳过同步,用本地已有凭证)
  wb2api-launcher -no-watch           不起后台对账
  wb2api-launcher -mode pull          只拉不推
  wb2api-launcher -version            打印版本

标志:
`

// usageTail 帮助文本后半段。
const usageTail = `
退出码:
  0  正常(网关退出时原样透出它的退出码)
  1  启动阶段失败
  2  -status 模式下存在未同步的差异

配置查找:
  config.json 默认取启动器同目录;相对路径一律以 config.json 所在目录为基准,
  网关子进程的工作目录也会被切到那里 —— 保证"同步写的目录"就是"网关读的目录"。
`

// parseFlags 解析命令行。返回 (选项, 是否只打印版本, error)。
func parseFlags(args []string) (options, bool, error) {
	var (
		opt         options
		showVersion bool
		modeStr     string
		statusOnly  bool
	)

	fs := flag.NewFlagSet("wb2api-launcher", flag.ContinueOnError)
	fs.SetOutput(os.Stderr)
	fs.Usage = func() {
		fmt.Fprint(os.Stderr, usageHead)
		fs.PrintDefaults()
		fmt.Fprint(os.Stderr, usageTail)
	}

	fs.StringVar(&opt.configPath, "config", "", "配置文件路径(默认:启动器同目录的 config.json)")
	fs.StringVar(&opt.serverBin, "server", "", "网关二进制路径(默认:同目录的 wb2api(.exe),再找 PATH)")

	fs.StringVar(&modeStr, "mode", "sync", "同步方向:sync / pull / push")
	fs.BoolVar(&statusOnly, "status", false, "只看差异不写入,也不启动网关(等价 -mode sync -dry-run -sync-only)")
	fs.BoolVar(&opt.syncOnly, "sync-only", false, "同步一次后退出,不启动网关")

	fs.BoolVar(&opt.noSync, "no-sync", false, "跳过同步,直接启动网关")
	fs.BoolVar(&opt.noWatch, "no-watch", false, "不起后台定时对账")
	fs.DurationVar(&opt.interval, "interval", 5*time.Minute, "后台对账间隔")
	fs.DurationVar(&opt.shutdownGrace, "shutdown-grace", 10*time.Second, "退出时等待网关优雅收尾的宽限期")
	fs.BoolVar(&opt.quiet, "quiet", false, "减少输出(仍会打印同步结果与告警)")

	// 同步参数覆盖(不写 config.json 也能跑)
	fs.StringVar(&opt.bucket, "bucket", "", "HF 桶 ID,如 a3216/wb2api-storage")
	fs.StringVar(&opt.token, "token", "", "HF token(更推荐写进 config.json 的 sync.token_file)")
	fs.StringVar(&opt.endpoint, "endpoint", "", "HF 端点(默认 https://huggingface.co)")
	fs.StringVar(&opt.include, "include-data", "", "额外同步的 data/ 文件名,逗号分隔(默认只同步凭证)")
	fs.StringVar(&opt.writer, "writer", "", "写入通道:auto / hf / relay")
	fs.StringVar(&opt.relayURL, "relay-url", "", "中转网关地址(relay 写入通道用)")
	fs.StringVar(&opt.restart, "restart-space", "", "推了新凭证后重启该 Space,如 a3216/gcli2api")
	fs.StringVar(&opt.caBundle, "ca-bundle", "", "额外 CA 证书包(PEM);本机跑抓包代理时给 hf CLI 用")
	fs.BoolVar(&opt.dryRun, "dry-run", false, "只展示将要做什么,不写任何东西")

	fs.BoolVar(&showVersion, "version", false, "打印版本后退出")

	if err := fs.Parse(args); err != nil {
		if err == flag.ErrHelp {
			// -h 走 Usage(已经打印过了),按成功退出。
			return opt, true, nil
		}
		return opt, false, err
	}
	if rest := fs.Args(); len(rest) > 0 {
		return opt, false, fmt.Errorf("未知参数: %s(用 -h 看帮助)", strings.Join(rest, " "))
	}
	if showVersion {
		return opt, true, nil
	}

	mode, reportOnly, err := syncer.ParseCommand(modeStr)
	if err != nil {
		return opt, false, err
	}
	opt.mode = mode
	// modeStr 写 "status" 时 ParseCommand 已告知只报告。
	opt.reportOnly = reportOnly

	// -status 等价于「按 sync 算一遍,但不落地、不启动网关」。
	if statusOnly {
		opt.mode = syncer.ModeSync
		opt.reportOnly = true
		opt.syncOnly = true
	}
	// 只报告就不可能写入:把 dry-run 也定死,避免出现自相矛盾的组合。
	if opt.reportOnly {
		opt.dryRun = true
	}
	return opt, false, nil
}