|
|
|
|
|
|
|
|
|
|
|
package main |
|
|
|
|
|
import ( |
|
|
"context" |
|
|
"errors" |
|
|
"flag" |
|
|
"fmt" |
|
|
"io/fs" |
|
|
"net/url" |
|
|
"os" |
|
|
"path/filepath" |
|
|
"strings" |
|
|
"time" |
|
|
|
|
|
"github.com/joho/godotenv" |
|
|
configaccess "github.com/router-for-me/CLIProxyAPI/v6/internal/access/config_access" |
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/buildinfo" |
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/cmd" |
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/logging" |
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/managementasset" |
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc" |
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/store" |
|
|
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator" |
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/usage" |
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util" |
|
|
sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" |
|
|
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" |
|
|
log "github.com/sirupsen/logrus" |
|
|
) |
|
|
|
|
|
var ( |
|
|
Version = "dev" |
|
|
Commit = "none" |
|
|
BuildDate = "unknown" |
|
|
DefaultConfigPath = "" |
|
|
) |
|
|
|
|
|
|
|
|
func init() { |
|
|
logging.SetupBaseLogger() |
|
|
buildinfo.Version = Version |
|
|
buildinfo.Commit = Commit |
|
|
buildinfo.BuildDate = BuildDate |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func setKiroIncognitoMode(cfg *config.Config, useIncognito, noIncognito bool) { |
|
|
if useIncognito { |
|
|
cfg.IncognitoBrowser = true |
|
|
} else if noIncognito { |
|
|
cfg.IncognitoBrowser = false |
|
|
} else { |
|
|
cfg.IncognitoBrowser = true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func main() { |
|
|
fmt.Printf("CLIProxyAPI Version: %s, Commit: %s, BuiltAt: %s\n", buildinfo.Version, buildinfo.Commit, buildinfo.BuildDate) |
|
|
|
|
|
|
|
|
var login bool |
|
|
var codexLogin bool |
|
|
var claudeLogin bool |
|
|
var qwenLogin bool |
|
|
var iflowLogin bool |
|
|
var iflowCookie bool |
|
|
var noBrowser bool |
|
|
var antigravityLogin bool |
|
|
var kiroLogin bool |
|
|
var kiroGoogleLogin bool |
|
|
var kiroAWSLogin bool |
|
|
var kiroAWSAuthCode bool |
|
|
var kiroImport bool |
|
|
var githubCopilotLogin bool |
|
|
var projectID string |
|
|
var vertexImport string |
|
|
var configPath string |
|
|
var password string |
|
|
var noIncognito bool |
|
|
var useIncognito bool |
|
|
|
|
|
|
|
|
flag.BoolVar(&login, "login", false, "Login Google Account") |
|
|
flag.BoolVar(&codexLogin, "codex-login", false, "Login to Codex using OAuth") |
|
|
flag.BoolVar(&claudeLogin, "claude-login", false, "Login to Claude using OAuth") |
|
|
flag.BoolVar(&qwenLogin, "qwen-login", false, "Login to Qwen using OAuth") |
|
|
flag.BoolVar(&iflowLogin, "iflow-login", false, "Login to iFlow using OAuth") |
|
|
flag.BoolVar(&iflowCookie, "iflow-cookie", false, "Login to iFlow using Cookie") |
|
|
flag.BoolVar(&noBrowser, "no-browser", false, "Don't open browser automatically for OAuth") |
|
|
flag.BoolVar(&useIncognito, "incognito", false, "Open browser in incognito/private mode for OAuth (useful for multiple accounts)") |
|
|
flag.BoolVar(&noIncognito, "no-incognito", false, "Force disable incognito mode (uses existing browser session)") |
|
|
flag.BoolVar(&antigravityLogin, "antigravity-login", false, "Login to Antigravity using OAuth") |
|
|
flag.BoolVar(&kiroLogin, "kiro-login", false, "Login to Kiro using Google OAuth") |
|
|
flag.BoolVar(&kiroGoogleLogin, "kiro-google-login", false, "Login to Kiro using Google OAuth (same as --kiro-login)") |
|
|
flag.BoolVar(&kiroAWSLogin, "kiro-aws-login", false, "Login to Kiro using AWS Builder ID (device code flow)") |
|
|
flag.BoolVar(&kiroAWSAuthCode, "kiro-aws-authcode", false, "Login to Kiro using AWS Builder ID (authorization code flow, better UX)") |
|
|
flag.BoolVar(&kiroImport, "kiro-import", false, "Import Kiro token from Kiro IDE (~/.aws/sso/cache/kiro-auth-token.json)") |
|
|
flag.BoolVar(&githubCopilotLogin, "github-copilot-login", false, "Login to GitHub Copilot using device flow") |
|
|
flag.StringVar(&projectID, "project_id", "", "Project ID (Gemini only, not required)") |
|
|
flag.StringVar(&configPath, "config", DefaultConfigPath, "Configure File Path") |
|
|
flag.StringVar(&vertexImport, "vertex-import", "", "Import Vertex service account key JSON file") |
|
|
flag.StringVar(&password, "password", "", "") |
|
|
|
|
|
flag.CommandLine.Usage = func() { |
|
|
out := flag.CommandLine.Output() |
|
|
_, _ = fmt.Fprintf(out, "Usage of %s\n", os.Args[0]) |
|
|
flag.CommandLine.VisitAll(func(f *flag.Flag) { |
|
|
if f.Name == "password" { |
|
|
return |
|
|
} |
|
|
s := fmt.Sprintf(" -%s", f.Name) |
|
|
name, unquoteUsage := flag.UnquoteUsage(f) |
|
|
if name != "" { |
|
|
s += " " + name |
|
|
} |
|
|
if len(s) <= 4 { |
|
|
s += " " |
|
|
} else { |
|
|
s += "\n " |
|
|
} |
|
|
if unquoteUsage != "" { |
|
|
s += unquoteUsage |
|
|
} |
|
|
if f.DefValue != "" && f.DefValue != "false" && f.DefValue != "0" { |
|
|
s += fmt.Sprintf(" (default %s)", f.DefValue) |
|
|
} |
|
|
_, _ = fmt.Fprint(out, s+"\n") |
|
|
}) |
|
|
} |
|
|
|
|
|
|
|
|
flag.Parse() |
|
|
|
|
|
|
|
|
var err error |
|
|
var cfg *config.Config |
|
|
var isCloudDeploy bool |
|
|
var ( |
|
|
usePostgresStore bool |
|
|
pgStoreDSN string |
|
|
pgStoreSchema string |
|
|
pgStoreLocalPath string |
|
|
pgStoreInst *store.PostgresStore |
|
|
useGitStore bool |
|
|
gitStoreRemoteURL string |
|
|
gitStoreUser string |
|
|
gitStorePassword string |
|
|
gitStoreLocalPath string |
|
|
gitStoreInst *store.GitTokenStore |
|
|
gitStoreRoot string |
|
|
useObjectStore bool |
|
|
objectStoreEndpoint string |
|
|
objectStoreAccess string |
|
|
objectStoreSecret string |
|
|
objectStoreBucket string |
|
|
objectStoreLocalPath string |
|
|
objectStoreInst *store.ObjectTokenStore |
|
|
) |
|
|
|
|
|
wd, err := os.Getwd() |
|
|
if err != nil { |
|
|
log.Errorf("failed to get working directory: %v", err) |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
if errLoad := godotenv.Load(filepath.Join(wd, ".env")); errLoad != nil { |
|
|
if !errors.Is(errLoad, os.ErrNotExist) { |
|
|
log.WithError(errLoad).Warn("failed to load .env file") |
|
|
} |
|
|
} |
|
|
|
|
|
lookupEnv := func(keys ...string) (string, bool) { |
|
|
for _, key := range keys { |
|
|
if value, ok := os.LookupEnv(key); ok { |
|
|
if trimmed := strings.TrimSpace(value); trimmed != "" { |
|
|
return trimmed, true |
|
|
} |
|
|
} |
|
|
} |
|
|
return "", false |
|
|
} |
|
|
writableBase := util.WritablePath() |
|
|
if value, ok := lookupEnv("PGSTORE_DSN", "pgstore_dsn"); ok { |
|
|
usePostgresStore = true |
|
|
pgStoreDSN = value |
|
|
} |
|
|
if usePostgresStore { |
|
|
if value, ok := lookupEnv("PGSTORE_SCHEMA", "pgstore_schema"); ok { |
|
|
pgStoreSchema = value |
|
|
} |
|
|
if value, ok := lookupEnv("PGSTORE_LOCAL_PATH", "pgstore_local_path"); ok { |
|
|
pgStoreLocalPath = value |
|
|
} |
|
|
if pgStoreLocalPath == "" { |
|
|
if writableBase != "" { |
|
|
pgStoreLocalPath = writableBase |
|
|
} else { |
|
|
pgStoreLocalPath = wd |
|
|
} |
|
|
} |
|
|
useGitStore = false |
|
|
} |
|
|
if value, ok := lookupEnv("GITSTORE_GIT_URL", "gitstore_git_url"); ok { |
|
|
useGitStore = true |
|
|
gitStoreRemoteURL = value |
|
|
} |
|
|
if value, ok := lookupEnv("GITSTORE_GIT_USERNAME", "gitstore_git_username"); ok { |
|
|
gitStoreUser = value |
|
|
} |
|
|
if value, ok := lookupEnv("GITSTORE_GIT_TOKEN", "gitstore_git_token"); ok { |
|
|
gitStorePassword = value |
|
|
} |
|
|
if value, ok := lookupEnv("GITSTORE_LOCAL_PATH", "gitstore_local_path"); ok { |
|
|
gitStoreLocalPath = value |
|
|
} |
|
|
if value, ok := lookupEnv("OBJECTSTORE_ENDPOINT", "objectstore_endpoint"); ok { |
|
|
useObjectStore = true |
|
|
objectStoreEndpoint = value |
|
|
} |
|
|
if value, ok := lookupEnv("OBJECTSTORE_ACCESS_KEY", "objectstore_access_key"); ok { |
|
|
objectStoreAccess = value |
|
|
} |
|
|
if value, ok := lookupEnv("OBJECTSTORE_SECRET_KEY", "objectstore_secret_key"); ok { |
|
|
objectStoreSecret = value |
|
|
} |
|
|
if value, ok := lookupEnv("OBJECTSTORE_BUCKET", "objectstore_bucket"); ok { |
|
|
objectStoreBucket = value |
|
|
} |
|
|
if value, ok := lookupEnv("OBJECTSTORE_LOCAL_PATH", "objectstore_local_path"); ok { |
|
|
objectStoreLocalPath = value |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
deployEnv := os.Getenv("DEPLOY") |
|
|
if deployEnv == "cloud" { |
|
|
isCloudDeploy = true |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var configFilePath string |
|
|
if usePostgresStore { |
|
|
if pgStoreLocalPath == "" { |
|
|
pgStoreLocalPath = wd |
|
|
} |
|
|
pgStoreLocalPath = filepath.Join(pgStoreLocalPath, "pgstore") |
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) |
|
|
pgStoreInst, err = store.NewPostgresStore(ctx, store.PostgresStoreConfig{ |
|
|
DSN: pgStoreDSN, |
|
|
Schema: pgStoreSchema, |
|
|
SpoolDir: pgStoreLocalPath, |
|
|
}) |
|
|
cancel() |
|
|
if err != nil { |
|
|
log.Errorf("failed to initialize postgres token store: %v", err) |
|
|
return |
|
|
} |
|
|
examplePath := filepath.Join(wd, "config.example.yaml") |
|
|
ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second) |
|
|
if errBootstrap := pgStoreInst.Bootstrap(ctx, examplePath); errBootstrap != nil { |
|
|
cancel() |
|
|
log.Errorf("failed to bootstrap postgres-backed config: %v", errBootstrap) |
|
|
return |
|
|
} |
|
|
cancel() |
|
|
configFilePath = pgStoreInst.ConfigPath() |
|
|
cfg, err = config.LoadConfigOptional(configFilePath, isCloudDeploy) |
|
|
if err == nil { |
|
|
cfg.AuthDir = pgStoreInst.AuthDir() |
|
|
log.Infof("postgres-backed token store enabled, workspace path: %s", pgStoreInst.WorkDir()) |
|
|
} |
|
|
} else if useObjectStore { |
|
|
if objectStoreLocalPath == "" { |
|
|
if writableBase != "" { |
|
|
objectStoreLocalPath = writableBase |
|
|
} else { |
|
|
objectStoreLocalPath = wd |
|
|
} |
|
|
} |
|
|
objectStoreRoot := filepath.Join(objectStoreLocalPath, "objectstore") |
|
|
resolvedEndpoint := strings.TrimSpace(objectStoreEndpoint) |
|
|
useSSL := true |
|
|
if strings.Contains(resolvedEndpoint, "://") { |
|
|
parsed, errParse := url.Parse(resolvedEndpoint) |
|
|
if errParse != nil { |
|
|
log.Errorf("failed to parse object store endpoint %q: %v", objectStoreEndpoint, errParse) |
|
|
return |
|
|
} |
|
|
switch strings.ToLower(parsed.Scheme) { |
|
|
case "http": |
|
|
useSSL = false |
|
|
case "https": |
|
|
useSSL = true |
|
|
default: |
|
|
log.Errorf("unsupported object store scheme %q (only http and https are allowed)", parsed.Scheme) |
|
|
return |
|
|
} |
|
|
if parsed.Host == "" { |
|
|
log.Errorf("object store endpoint %q is missing host information", objectStoreEndpoint) |
|
|
return |
|
|
} |
|
|
resolvedEndpoint = parsed.Host |
|
|
if parsed.Path != "" && parsed.Path != "/" { |
|
|
resolvedEndpoint = strings.TrimSuffix(parsed.Host+parsed.Path, "/") |
|
|
} |
|
|
} |
|
|
resolvedEndpoint = strings.TrimRight(resolvedEndpoint, "/") |
|
|
objCfg := store.ObjectStoreConfig{ |
|
|
Endpoint: resolvedEndpoint, |
|
|
Bucket: objectStoreBucket, |
|
|
AccessKey: objectStoreAccess, |
|
|
SecretKey: objectStoreSecret, |
|
|
LocalRoot: objectStoreRoot, |
|
|
UseSSL: useSSL, |
|
|
PathStyle: true, |
|
|
} |
|
|
objectStoreInst, err = store.NewObjectTokenStore(objCfg) |
|
|
if err != nil { |
|
|
log.Errorf("failed to initialize object token store: %v", err) |
|
|
return |
|
|
} |
|
|
examplePath := filepath.Join(wd, "config.example.yaml") |
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) |
|
|
if errBootstrap := objectStoreInst.Bootstrap(ctx, examplePath); errBootstrap != nil { |
|
|
cancel() |
|
|
log.Errorf("failed to bootstrap object-backed config: %v", errBootstrap) |
|
|
return |
|
|
} |
|
|
cancel() |
|
|
configFilePath = objectStoreInst.ConfigPath() |
|
|
cfg, err = config.LoadConfigOptional(configFilePath, isCloudDeploy) |
|
|
if err == nil { |
|
|
if cfg == nil { |
|
|
cfg = &config.Config{} |
|
|
} |
|
|
cfg.AuthDir = objectStoreInst.AuthDir() |
|
|
log.Infof("object-backed token store enabled, bucket: %s", objectStoreBucket) |
|
|
} |
|
|
} else if useGitStore { |
|
|
if gitStoreLocalPath == "" { |
|
|
if writableBase != "" { |
|
|
gitStoreLocalPath = writableBase |
|
|
} else { |
|
|
gitStoreLocalPath = wd |
|
|
} |
|
|
} |
|
|
gitStoreRoot = filepath.Join(gitStoreLocalPath, "gitstore") |
|
|
authDir := filepath.Join(gitStoreRoot, "auths") |
|
|
gitStoreInst = store.NewGitTokenStore(gitStoreRemoteURL, gitStoreUser, gitStorePassword) |
|
|
gitStoreInst.SetBaseDir(authDir) |
|
|
if errRepo := gitStoreInst.EnsureRepository(); errRepo != nil { |
|
|
log.Errorf("failed to prepare git token store: %v", errRepo) |
|
|
return |
|
|
} |
|
|
configFilePath = gitStoreInst.ConfigPath() |
|
|
if configFilePath == "" { |
|
|
configFilePath = filepath.Join(gitStoreRoot, "config", "config.yaml") |
|
|
} |
|
|
if _, statErr := os.Stat(configFilePath); errors.Is(statErr, fs.ErrNotExist) { |
|
|
examplePath := filepath.Join(wd, "config.example.yaml") |
|
|
if _, errExample := os.Stat(examplePath); errExample != nil { |
|
|
log.Errorf("failed to find template config file: %v", errExample) |
|
|
return |
|
|
} |
|
|
if errCopy := misc.CopyConfigTemplate(examplePath, configFilePath); errCopy != nil { |
|
|
log.Errorf("failed to bootstrap git-backed config: %v", errCopy) |
|
|
return |
|
|
} |
|
|
if errCommit := gitStoreInst.PersistConfig(context.Background()); errCommit != nil { |
|
|
log.Errorf("failed to commit initial git-backed config: %v", errCommit) |
|
|
return |
|
|
} |
|
|
log.Infof("git-backed config initialized from template: %s", configFilePath) |
|
|
} else if statErr != nil { |
|
|
log.Errorf("failed to inspect git-backed config: %v", statErr) |
|
|
return |
|
|
} |
|
|
cfg, err = config.LoadConfigOptional(configFilePath, isCloudDeploy) |
|
|
if err == nil { |
|
|
cfg.AuthDir = gitStoreInst.AuthDir() |
|
|
log.Infof("git-backed token store enabled, repository path: %s", gitStoreRoot) |
|
|
} |
|
|
} else if configPath != "" { |
|
|
configFilePath = configPath |
|
|
cfg, err = config.LoadConfigOptional(configPath, isCloudDeploy) |
|
|
} else { |
|
|
wd, err = os.Getwd() |
|
|
if err != nil { |
|
|
log.Errorf("failed to get working directory: %v", err) |
|
|
return |
|
|
} |
|
|
configFilePath = filepath.Join(wd, "config.yaml") |
|
|
cfg, err = config.LoadConfigOptional(configFilePath, isCloudDeploy) |
|
|
} |
|
|
if err != nil { |
|
|
log.Errorf("failed to load config: %v", err) |
|
|
return |
|
|
} |
|
|
if cfg == nil { |
|
|
cfg = &config.Config{} |
|
|
} |
|
|
|
|
|
|
|
|
var configFileExists bool |
|
|
if isCloudDeploy { |
|
|
if info, errStat := os.Stat(configFilePath); errStat != nil { |
|
|
|
|
|
log.Info("Cloud deploy mode: No configuration file detected; standing by for configuration") |
|
|
configFileExists = false |
|
|
} else if info.IsDir() { |
|
|
log.Info("Cloud deploy mode: Config path is a directory; standing by for configuration") |
|
|
configFileExists = false |
|
|
} else if cfg.Port == 0 { |
|
|
|
|
|
|
|
|
log.Info("Cloud deploy mode: Configuration file is empty or invalid; standing by for valid configuration") |
|
|
configFileExists = false |
|
|
} else { |
|
|
log.Info("Cloud deploy mode: Configuration file detected; starting service") |
|
|
configFileExists = true |
|
|
} |
|
|
} |
|
|
usage.SetStatisticsEnabled(cfg.UsageStatisticsEnabled) |
|
|
coreauth.SetQuotaCooldownDisabled(cfg.DisableCooling) |
|
|
|
|
|
if err = logging.ConfigureLogOutput(cfg); err != nil { |
|
|
log.Errorf("failed to configure log output: %v", err) |
|
|
return |
|
|
} |
|
|
|
|
|
log.Infof("CLIProxyAPI Version: %s, Commit: %s, BuiltAt: %s", buildinfo.Version, buildinfo.Commit, buildinfo.BuildDate) |
|
|
|
|
|
|
|
|
util.SetLogLevel(cfg) |
|
|
|
|
|
if resolvedAuthDir, errResolveAuthDir := util.ResolveAuthDir(cfg.AuthDir); errResolveAuthDir != nil { |
|
|
log.Errorf("failed to resolve auth directory: %v", errResolveAuthDir) |
|
|
return |
|
|
} else { |
|
|
cfg.AuthDir = resolvedAuthDir |
|
|
} |
|
|
managementasset.SetCurrentConfig(cfg) |
|
|
|
|
|
|
|
|
options := &cmd.LoginOptions{ |
|
|
NoBrowser: noBrowser, |
|
|
} |
|
|
|
|
|
|
|
|
if usePostgresStore { |
|
|
sdkAuth.RegisterTokenStore(pgStoreInst) |
|
|
} else if useObjectStore { |
|
|
sdkAuth.RegisterTokenStore(objectStoreInst) |
|
|
} else if useGitStore { |
|
|
sdkAuth.RegisterTokenStore(gitStoreInst) |
|
|
} else { |
|
|
sdkAuth.RegisterTokenStore(sdkAuth.NewFileTokenStore()) |
|
|
} |
|
|
|
|
|
|
|
|
configaccess.Register() |
|
|
|
|
|
|
|
|
|
|
|
if vertexImport != "" { |
|
|
|
|
|
cmd.DoVertexImport(cfg, vertexImport) |
|
|
} else if login { |
|
|
|
|
|
cmd.DoLogin(cfg, projectID, options) |
|
|
} else if antigravityLogin { |
|
|
|
|
|
cmd.DoAntigravityLogin(cfg, options) |
|
|
} else if githubCopilotLogin { |
|
|
|
|
|
cmd.DoGitHubCopilotLogin(cfg, options) |
|
|
} else if codexLogin { |
|
|
|
|
|
cmd.DoCodexLogin(cfg, options) |
|
|
} else if claudeLogin { |
|
|
|
|
|
cmd.DoClaudeLogin(cfg, options) |
|
|
} else if qwenLogin { |
|
|
cmd.DoQwenLogin(cfg, options) |
|
|
} else if iflowLogin { |
|
|
cmd.DoIFlowLogin(cfg, options) |
|
|
} else if iflowCookie { |
|
|
cmd.DoIFlowCookieAuth(cfg, options) |
|
|
} else if kiroLogin { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setKiroIncognitoMode(cfg, useIncognito, noIncognito) |
|
|
cmd.DoKiroLogin(cfg, options) |
|
|
} else if kiroGoogleLogin { |
|
|
|
|
|
|
|
|
|
|
|
setKiroIncognitoMode(cfg, useIncognito, noIncognito) |
|
|
cmd.DoKiroGoogleLogin(cfg, options) |
|
|
} else if kiroAWSLogin { |
|
|
|
|
|
|
|
|
setKiroIncognitoMode(cfg, useIncognito, noIncognito) |
|
|
cmd.DoKiroAWSLogin(cfg, options) |
|
|
} else if kiroAWSAuthCode { |
|
|
|
|
|
setKiroIncognitoMode(cfg, useIncognito, noIncognito) |
|
|
cmd.DoKiroAWSAuthCodeLogin(cfg, options) |
|
|
} else if kiroImport { |
|
|
cmd.DoKiroImport(cfg, options) |
|
|
} else { |
|
|
|
|
|
if isCloudDeploy && !configFileExists { |
|
|
|
|
|
cmd.WaitForCloudDeploy() |
|
|
return |
|
|
} |
|
|
|
|
|
managementasset.StartAutoUpdater(context.Background(), configFilePath) |
|
|
cmd.StartService(cfg, configFilePath, password) |
|
|
} |
|
|
} |
|
|
|