mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-05-19 17:01:48 +08:00
feat(server): add support for loading configuration from a remote home control plane
- Introduced `-home` and `-home-password` flags for specifying home control plane address and authentication. - Implemented fetching and parsing configuration from the home control plane when `-home` is used. - Adjusted server configuration handling to bypass local config files when loading from home. - Ensured compatibility with cloud deploy mode and validation of home configurations.
This commit is contained in:
@@ -10,9 +10,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -21,6 +23,7 @@ import (
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/buildinfo"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/cmd"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/home"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/managementasset"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/misc"
|
||||
@@ -70,6 +73,8 @@ func main() {
|
||||
var vertexImportPrefix string
|
||||
var configPath string
|
||||
var password string
|
||||
var homeAddr string
|
||||
var homePassword string
|
||||
var tuiMode bool
|
||||
var standalone bool
|
||||
var localModel bool
|
||||
@@ -88,6 +93,8 @@ func main() {
|
||||
flag.StringVar(&vertexImport, "vertex-import", "", "Import Vertex service account key JSON file")
|
||||
flag.StringVar(&vertexImportPrefix, "vertex-import-prefix", "", "Prefix for Vertex model namespacing (use with -vertex-import)")
|
||||
flag.StringVar(&password, "password", "", "")
|
||||
flag.StringVar(&homeAddr, "home", "", "Home control plane address in host:port format (loads config from home and skips local config file)")
|
||||
flag.StringVar(&homePassword, "home-password", "", "Home control plane password (Redis AUTH)")
|
||||
flag.BoolVar(&tuiMode, "tui", false, "Start with terminal management UI")
|
||||
flag.BoolVar(&standalone, "standalone", false, "In TUI mode, start an embedded local server")
|
||||
flag.BoolVar(&localModel, "local-model", false, "Use embedded model catalog only, skip remote model fetching")
|
||||
@@ -126,6 +133,7 @@ func main() {
|
||||
var err error
|
||||
var cfg *config.Config
|
||||
var isCloudDeploy bool
|
||||
var configLoadedFromHome bool
|
||||
var (
|
||||
usePostgresStore bool
|
||||
pgStoreDSN string
|
||||
@@ -236,7 +244,67 @@ func main() {
|
||||
// Determine and load the configuration file.
|
||||
// Prefer the Postgres store when configured, otherwise fallback to git or local files.
|
||||
var configFilePath string
|
||||
if usePostgresStore {
|
||||
if strings.TrimSpace(homeAddr) != "" {
|
||||
configLoadedFromHome = true
|
||||
trimmedHomePassword := strings.TrimSpace(homePassword)
|
||||
host, portStr, errSplit := net.SplitHostPort(strings.TrimSpace(homeAddr))
|
||||
if errSplit != nil {
|
||||
log.Errorf("invalid -home address %q (expected host:port): %v", homeAddr, errSplit)
|
||||
return
|
||||
}
|
||||
host = strings.TrimSpace(host)
|
||||
if host == "" {
|
||||
log.Errorf("invalid -home address %q: host is empty", homeAddr)
|
||||
return
|
||||
}
|
||||
port, errPort := strconv.Atoi(strings.TrimSpace(portStr))
|
||||
if errPort != nil || port <= 0 {
|
||||
log.Errorf("invalid -home address %q: invalid port %q", homeAddr, portStr)
|
||||
return
|
||||
}
|
||||
|
||||
homeCfg := config.HomeConfig{
|
||||
Enabled: true,
|
||||
Host: host,
|
||||
Port: port,
|
||||
Password: trimmedHomePassword,
|
||||
}
|
||||
homeClient := home.New(homeCfg)
|
||||
defer homeClient.Close()
|
||||
|
||||
ctxHome, cancelHome := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
raw, errGetConfig := homeClient.GetConfig(ctxHome)
|
||||
cancelHome()
|
||||
if errGetConfig != nil {
|
||||
log.Errorf("failed to fetch config from home: %v", errGetConfig)
|
||||
return
|
||||
}
|
||||
|
||||
parsed, errParseConfig := config.ParseConfigBytes(raw)
|
||||
if errParseConfig != nil {
|
||||
log.Errorf("failed to parse config payload from home: %v", errParseConfig)
|
||||
return
|
||||
}
|
||||
if parsed == nil {
|
||||
parsed = &config.Config{}
|
||||
}
|
||||
parsed.Home = homeCfg
|
||||
parsed.Port = 8317 // Default to 8317 for home mode, can be overridden by home config
|
||||
cfg = parsed
|
||||
|
||||
// Keep a non-empty config path for downstream components (log paths, management assets, etc),
|
||||
// but do not require the file to exist when loading config from home.
|
||||
if strings.TrimSpace(configPath) != "" {
|
||||
configFilePath = configPath
|
||||
} else {
|
||||
configFilePath = filepath.Join(wd, "config.yaml")
|
||||
}
|
||||
|
||||
// Local stores are intentionally disabled when config is loaded from home.
|
||||
usePostgresStore = false
|
||||
useObjectStore = false
|
||||
useGitStore = false
|
||||
} else if usePostgresStore {
|
||||
if pgStoreLocalPath == "" {
|
||||
pgStoreLocalPath = wd
|
||||
}
|
||||
@@ -400,21 +468,25 @@ func main() {
|
||||
// In cloud deploy mode, check if we have a valid configuration
|
||||
var configFileExists bool
|
||||
if isCloudDeploy {
|
||||
if info, errStat := os.Stat(configFilePath); errStat != nil {
|
||||
// Don't mislead: API server will not start until configuration is provided.
|
||||
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 {
|
||||
// LoadConfigOptional returns empty config when file is empty or invalid.
|
||||
// Config file exists but is empty or invalid; treat as missing config
|
||||
log.Info("Cloud deploy mode: Configuration file is empty or invalid; standing by for valid configuration")
|
||||
configFileExists = false
|
||||
if configLoadedFromHome && cfg != nil {
|
||||
configFileExists = cfg.Port != 0
|
||||
} else {
|
||||
log.Info("Cloud deploy mode: Configuration file detected; starting service")
|
||||
configFileExists = true
|
||||
if info, errStat := os.Stat(configFilePath); errStat != nil {
|
||||
// Don't mislead: API server will not start until configuration is provided.
|
||||
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 {
|
||||
// LoadConfigOptional returns empty config when file is empty or invalid.
|
||||
// Config file exists but is empty or invalid; treat as missing config
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
redisqueue.SetUsageStatisticsEnabled(cfg.UsageStatisticsEnabled)
|
||||
|
||||
Reference in New Issue
Block a user