gcli2api / cmd /launcher /flags.go
a3216's picture
chore: 同步到上游 1.12.0-panel + 凭证同步/独立启动器/保活
6d60378 verified
Raw History Blame Contribute Delete
4.66 kB
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
}