@@ -10,29 +10,32 @@ import (
1010 "fmt"
1111 "io"
1212 "io/fs"
13+ "net"
1314 "net/url"
1415 "os"
1516 "path/filepath"
17+ "strconv"
1618 "strings"
1719 "time"
1820
1921 "github.com/joho/godotenv"
20- configaccess "github.com/router-for-me/CLIProxyAPI/v6/internal/access/config_access"
21- "github.com/router-for-me/CLIProxyAPI/v6/internal/buildinfo"
22- "github.com/router-for-me/CLIProxyAPI/v6/internal/cmd"
23- "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
24- "github.com/router-for-me/CLIProxyAPI/v6/internal/logging"
25- "github.com/router-for-me/CLIProxyAPI/v6/internal/managementasset"
26- "github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
27- "github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue"
28- "github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
29- "github.com/router-for-me/CLIProxyAPI/v6/internal/store"
30- _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator"
31- "github.com/router-for-me/CLIProxyAPI/v6/internal/tui"
32- "github.com/router-for-me/CLIProxyAPI/v6/internal/usage"
33- "github.com/router-for-me/CLIProxyAPI/v6/internal/util"
34- sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
35- coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
22+ configaccess "github.com/router-for-me/CLIProxyAPI/v7/internal/access/config_access"
23+ "github.com/router-for-me/CLIProxyAPI/v7/internal/buildinfo"
24+ "github.com/router-for-me/CLIProxyAPI/v7/internal/cmd"
25+ "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
26+ "github.com/router-for-me/CLIProxyAPI/v7/internal/home"
27+ "github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
28+ "github.com/router-for-me/CLIProxyAPI/v7/internal/managementasset"
29+ "github.com/router-for-me/CLIProxyAPI/v7/internal/misc"
30+ "github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue"
31+ "github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
32+ "github.com/router-for-me/CLIProxyAPI/v7/internal/store"
33+ _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator"
34+ "github.com/router-for-me/CLIProxyAPI/v7/internal/tui"
35+ "github.com/router-for-me/CLIProxyAPI/v7/internal/usage"
36+ "github.com/router-for-me/CLIProxyAPI/v7/internal/util"
37+ sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth"
38+ coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
3639 log "github.com/sirupsen/logrus"
3740)
3841
@@ -71,6 +74,8 @@ func main() {
7174 var vertexImportPrefix string
7275 var configPath string
7376 var password string
77+ var homeAddr string
78+ var homePassword string
7479 var tuiMode bool
7580 var standalone bool
7681 var localModel bool
@@ -89,6 +94,8 @@ func main() {
8994 flag .StringVar (& vertexImport , "vertex-import" , "" , "Import Vertex service account key JSON file" )
9095 flag .StringVar (& vertexImportPrefix , "vertex-import-prefix" , "" , "Prefix for Vertex model namespacing (use with -vertex-import)" )
9196 flag .StringVar (& password , "password" , "" , "" )
97+ flag .StringVar (& homeAddr , "home" , "" , "Home control plane address in host:port format (loads config from home and skips local config file)" )
98+ flag .StringVar (& homePassword , "home-password" , "" , "Home control plane password (Redis AUTH)" )
9299 flag .BoolVar (& tuiMode , "tui" , false , "Start with terminal management UI" )
93100 flag .BoolVar (& standalone , "standalone" , false , "In TUI mode, start an embedded local server" )
94101 flag .BoolVar (& localModel , "local-model" , false , "Use embedded model catalog only, skip remote model fetching" )
@@ -127,6 +134,7 @@ func main() {
127134 var err error
128135 var cfg * config.Config
129136 var isCloudDeploy bool
137+ var configLoadedFromHome bool
130138 var (
131139 usePostgresStore bool
132140 pgStoreDSN string
@@ -237,7 +245,68 @@ func main() {
237245 // Determine and load the configuration file.
238246 // Prefer the Postgres store when configured, otherwise fallback to git or local files.
239247 var configFilePath string
240- if usePostgresStore {
248+ if strings .TrimSpace (homeAddr ) != "" {
249+ configLoadedFromHome = true
250+ trimmedHomePassword := strings .TrimSpace (homePassword )
251+ host , portStr , errSplit := net .SplitHostPort (strings .TrimSpace (homeAddr ))
252+ if errSplit != nil {
253+ log .Errorf ("invalid -home address %q (expected host:port): %v" , homeAddr , errSplit )
254+ return
255+ }
256+ host = strings .TrimSpace (host )
257+ if host == "" {
258+ log .Errorf ("invalid -home address %q: host is empty" , homeAddr )
259+ return
260+ }
261+ port , errPort := strconv .Atoi (strings .TrimSpace (portStr ))
262+ if errPort != nil || port <= 0 {
263+ log .Errorf ("invalid -home address %q: invalid port %q" , homeAddr , portStr )
264+ return
265+ }
266+
267+ homeCfg := config.HomeConfig {
268+ Enabled : true ,
269+ Host : host ,
270+ Port : port ,
271+ Password : trimmedHomePassword ,
272+ }
273+ homeClient := home .New (homeCfg )
274+ defer homeClient .Close ()
275+
276+ ctxHome , cancelHome := context .WithTimeout (context .Background (), 30 * time .Second )
277+ raw , errGetConfig := homeClient .GetConfig (ctxHome )
278+ cancelHome ()
279+ if errGetConfig != nil {
280+ log .Errorf ("failed to fetch config from home: %v" , errGetConfig )
281+ return
282+ }
283+
284+ parsed , errParseConfig := config .ParseConfigBytes (raw )
285+ if errParseConfig != nil {
286+ log .Errorf ("failed to parse config payload from home: %v" , errParseConfig )
287+ return
288+ }
289+ if parsed == nil {
290+ parsed = & config.Config {}
291+ }
292+ parsed .Home = homeCfg
293+ parsed .Port = 8317 // Default to 8317 for home mode, can be overridden by home config
294+ parsed .UsageStatisticsEnabled = true
295+ cfg = parsed
296+
297+ // Keep a non-empty config path for downstream components (log paths, management assets, etc),
298+ // but do not require the file to exist when loading config from home.
299+ if strings .TrimSpace (configPath ) != "" {
300+ configFilePath = configPath
301+ } else {
302+ configFilePath = filepath .Join (wd , "config.yaml" )
303+ }
304+
305+ // Local stores are intentionally disabled when config is loaded from home.
306+ usePostgresStore = false
307+ useObjectStore = false
308+ useGitStore = false
309+ } else if usePostgresStore {
241310 if pgStoreLocalPath == "" {
242311 pgStoreLocalPath = wd
243312 }
@@ -401,21 +470,25 @@ func main() {
401470 // In cloud deploy mode, check if we have a valid configuration
402471 var configFileExists bool
403472 if isCloudDeploy {
404- if info , errStat := os .Stat (configFilePath ); errStat != nil {
405- // Don't mislead: API server will not start until configuration is provided.
406- log .Info ("Cloud deploy mode: No configuration file detected; standing by for configuration" )
407- configFileExists = false
408- } else if info .IsDir () {
409- log .Info ("Cloud deploy mode: Config path is a directory; standing by for configuration" )
410- configFileExists = false
411- } else if cfg .Port == 0 {
412- // LoadConfigOptional returns empty config when file is empty or invalid.
413- // Config file exists but is empty or invalid; treat as missing config
414- log .Info ("Cloud deploy mode: Configuration file is empty or invalid; standing by for valid configuration" )
415- configFileExists = false
473+ if configLoadedFromHome && cfg != nil {
474+ configFileExists = cfg .Port != 0
416475 } else {
417- log .Info ("Cloud deploy mode: Configuration file detected; starting service" )
418- configFileExists = true
476+ if info , errStat := os .Stat (configFilePath ); errStat != nil {
477+ // Don't mislead: API server will not start until configuration is provided.
478+ log .Info ("Cloud deploy mode: No configuration file detected; standing by for configuration" )
479+ configFileExists = false
480+ } else if info .IsDir () {
481+ log .Info ("Cloud deploy mode: Config path is a directory; standing by for configuration" )
482+ configFileExists = false
483+ } else if cfg .Port == 0 {
484+ // LoadConfigOptional returns empty config when file is empty or invalid.
485+ // Config file exists but is empty or invalid; treat as missing config
486+ log .Info ("Cloud deploy mode: Configuration file is empty or invalid; standing by for valid configuration" )
487+ configFileExists = false
488+ } else {
489+ log .Info ("Cloud deploy mode: Configuration file detected; starting service" )
490+ configFileExists = true
491+ }
419492 }
420493 }
421494 usage .SetStatisticsEnabled (cfg .UsageStatisticsEnabled )
@@ -488,8 +561,11 @@ func main() {
488561 if standalone {
489562 // Standalone mode: start an embedded local server and connect TUI client to it.
490563 managementasset .StartAutoUpdater (context .Background (), configFilePath )
491- if ! localModel {
564+ misc .StartAntigravityVersionUpdater (context .Background ())
565+ if ! localModel && ! cfg .Home .Enabled {
492566 registry .StartModelsUpdater (context .Background ())
567+ } else if cfg .Home .Enabled {
568+ log .Info ("Home mode: remote model updates disabled" )
493569 }
494570 hook := tui .NewLogHook (2000 )
495571 hook .SetFormatter (& logging.LogFormatter {})
@@ -563,8 +639,11 @@ func main() {
563639 } else {
564640 // Start the main proxy service
565641 managementasset .StartAutoUpdater (context .Background (), configFilePath )
566- if ! localModel {
642+ misc .StartAntigravityVersionUpdater (context .Background ())
643+ if ! localModel && ! cfg .Home .Enabled {
567644 registry .StartModelsUpdater (context .Background ())
645+ } else if cfg .Home .Enabled {
646+ log .Info ("Home mode: remote model updates disabled" )
568647 }
569648 cmd .StartService (cfg , configFilePath , password )
570649 }
0 commit comments