mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
Add file watcher for dynamic configuration and client reloading
- Introduced `Watcher` for monitoring updates to the configuration file and authentication directory. - Integrated file watching into `StartService` to handle dynamic changes without restarting. - Enhanced API server and handlers to support client and configuration updates. - Updated `.gitignore` to include `docs/` directory. - Modified go dependencies to include `fsnotify` for the file watcher.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
config.yaml
|
config.yaml
|
||||||
|
docs/
|
||||||
@@ -63,14 +63,17 @@ func main() {
|
|||||||
var wd string
|
var wd string
|
||||||
|
|
||||||
// Load configuration from the specified path or the default path.
|
// Load configuration from the specified path or the default path.
|
||||||
|
var configFilePath string
|
||||||
if configPath != "" {
|
if configPath != "" {
|
||||||
|
configFilePath = configPath
|
||||||
cfg, err = config.LoadConfig(configPath)
|
cfg, err = config.LoadConfig(configPath)
|
||||||
} else {
|
} else {
|
||||||
wd, err = os.Getwd()
|
wd, err = os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to get working directory: %v", err)
|
log.Fatalf("failed to get working directory: %v", err)
|
||||||
}
|
}
|
||||||
cfg, err = config.LoadConfig(path.Join(wd, "config.yaml"))
|
configFilePath = path.Join(wd, "config.yaml")
|
||||||
|
cfg, err = config.LoadConfig(configFilePath)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to load config: %v", err)
|
log.Fatalf("failed to load config: %v", err)
|
||||||
@@ -102,6 +105,6 @@ func main() {
|
|||||||
if login {
|
if login {
|
||||||
cmd.DoLogin(cfg, projectID)
|
cmd.DoLogin(cfg, projectID)
|
||||||
} else {
|
} else {
|
||||||
cmd.StartService(cfg)
|
cmd.StartService(cfg, configFilePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -8,6 +8,7 @@ require (
|
|||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||||
github.com/tidwall/gjson v1.18.0
|
github.com/tidwall/gjson v1.18.0
|
||||||
github.com/tidwall/sjson v1.2.5
|
github.com/tidwall/sjson v1.2.5
|
||||||
|
golang.org/x/net v0.37.1-0.20250305215238-2914f4677317
|
||||||
golang.org/x/oauth2 v0.30.0
|
golang.org/x/oauth2 v0.30.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
@@ -18,6 +19,7 @@ require (
|
|||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
@@ -37,7 +39,6 @@ require (
|
|||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/arch v0.8.0 // indirect
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
golang.org/x/crypto v0.36.0 // indirect
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
golang.org/x/net v0.37.1-0.20250305215238-2914f4677317 // indirect
|
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -11,6 +11,8 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ func NewAPIHandlers(cliClients []*client.Client, cfg *config.Config) *APIHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateClients updates the handlers' client list and configuration
|
||||||
|
func (h *APIHandlers) UpdateClients(clients []*client.Client, cfg *config.Config) {
|
||||||
|
h.cliClients = clients
|
||||||
|
h.cfg = cfg
|
||||||
|
}
|
||||||
|
|
||||||
// Models handles the /v1/models endpoint.
|
// Models handles the /v1/models endpoint.
|
||||||
// It returns a hardcoded list of available AI models.
|
// It returns a hardcoded list of available AI models.
|
||||||
func (h *APIHandlers) Models(c *gin.Context) {
|
func (h *APIHandlers) Models(c *gin.Context) {
|
||||||
|
|||||||
@@ -139,6 +139,13 @@ func corsMiddleware() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateClients updates the server's client list and configuration
|
||||||
|
func (s *Server) UpdateClients(clients []*client.Client, cfg *config.Config) {
|
||||||
|
s.cfg = cfg
|
||||||
|
s.handlers.UpdateClients(clients, cfg)
|
||||||
|
log.Infof("server clients and configuration updated: %d clients", len(clients))
|
||||||
|
}
|
||||||
|
|
||||||
// AuthMiddleware returns a Gin middleware handler that authenticates requests
|
// AuthMiddleware returns a Gin middleware handler that authenticates requests
|
||||||
// using API keys. If no API keys are configured, it allows all requests.
|
// using API keys. If no API keys are configured, it allows all requests.
|
||||||
func AuthMiddleware(cfg *config.Config) gin.HandlerFunc {
|
func AuthMiddleware(cfg *config.Config) gin.HandlerFunc {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/luispater/CLIProxyAPI/internal/client"
|
"github.com/luispater/CLIProxyAPI/internal/client"
|
||||||
"github.com/luispater/CLIProxyAPI/internal/config"
|
"github.com/luispater/CLIProxyAPI/internal/config"
|
||||||
"github.com/luispater/CLIProxyAPI/internal/util"
|
"github.com/luispater/CLIProxyAPI/internal/util"
|
||||||
|
"github.com/luispater/CLIProxyAPI/internal/watcher"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -22,7 +23,7 @@ import (
|
|||||||
// StartService initializes and starts the main API proxy service.
|
// StartService initializes and starts the main API proxy service.
|
||||||
// It loads all available authentication tokens, creates a pool of clients,
|
// It loads all available authentication tokens, creates a pool of clients,
|
||||||
// starts the API server, and handles graceful shutdown signals.
|
// starts the API server, and handles graceful shutdown signals.
|
||||||
func StartService(cfg *config.Config) {
|
func StartService(cfg *config.Config, configPath string) {
|
||||||
// Create a pool of API clients, one for each token file found.
|
// Create a pool of API clients, one for each token file found.
|
||||||
cliClients := make([]*client.Client, 0)
|
cliClients := make([]*client.Client, 0)
|
||||||
err := filepath.Walk(cfg.AuthDir, func(path string, info fs.FileInfo, err error) error {
|
err := filepath.Walk(cfg.AuthDir, func(path string, info fs.FileInfo, err error) error {
|
||||||
@@ -82,10 +83,46 @@ func StartService(cfg *config.Config) {
|
|||||||
// Create and start the API server with the pool of clients.
|
// Create and start the API server with the pool of clients.
|
||||||
apiServer := api.NewServer(cfg, cliClients)
|
apiServer := api.NewServer(cfg, cliClients)
|
||||||
log.Infof("Starting API server on port %d", cfg.Port)
|
log.Infof("Starting API server on port %d", cfg.Port)
|
||||||
if err = apiServer.Start(); err != nil {
|
|
||||||
log.Fatalf("API server failed to start: %v", err)
|
// Start the API server in a goroutine so it doesn't block the main thread
|
||||||
|
go func() {
|
||||||
|
if err = apiServer.Start(); err != nil {
|
||||||
|
log.Fatalf("API server failed to start: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Give the server a moment to start up
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
log.Info("API server started successfully")
|
||||||
|
|
||||||
|
// Setup file watcher for config and auth directory changes
|
||||||
|
fileWatcher, errNewWatcher := watcher.NewWatcher(configPath, cfg.AuthDir, func(newClients []*client.Client, newCfg *config.Config) {
|
||||||
|
// Update the API server with new clients and configuration
|
||||||
|
apiServer.UpdateClients(newClients, newCfg)
|
||||||
|
})
|
||||||
|
if errNewWatcher != nil {
|
||||||
|
log.Fatalf("failed to create file watcher: %v", errNewWatcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set initial state for the watcher
|
||||||
|
fileWatcher.SetConfig(cfg)
|
||||||
|
fileWatcher.SetClients(cliClients)
|
||||||
|
|
||||||
|
// Start the file watcher
|
||||||
|
watcherCtx, watcherCancel := context.WithCancel(context.Background())
|
||||||
|
if errStartWatcher := fileWatcher.Start(watcherCtx); errStartWatcher != nil {
|
||||||
|
log.Fatalf("failed to start file watcher: %v", errStartWatcher)
|
||||||
|
}
|
||||||
|
log.Info("file watcher started for config and auth directory changes")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
watcherCancel()
|
||||||
|
errStopWatcher := fileWatcher.Stop()
|
||||||
|
if errStopWatcher != nil {
|
||||||
|
log.Errorf("error stopping file watcher: %v", errStopWatcher)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Set up a channel to listen for OS signals for graceful shutdown.
|
// Set up a channel to listen for OS signals for graceful shutdown.
|
||||||
sigChan := make(chan os.Signal, 1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|||||||
282
internal/watcher/watcher.go
Normal file
282
internal/watcher/watcher.go
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
package watcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/luispater/CLIProxyAPI/internal/auth"
|
||||||
|
"github.com/luispater/CLIProxyAPI/internal/client"
|
||||||
|
"github.com/luispater/CLIProxyAPI/internal/config"
|
||||||
|
"github.com/luispater/CLIProxyAPI/internal/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher manages file watching for configuration and authentication files
|
||||||
|
type Watcher struct {
|
||||||
|
configPath string
|
||||||
|
authDir string
|
||||||
|
config *config.Config
|
||||||
|
clients []*client.Client
|
||||||
|
clientsMutex sync.RWMutex
|
||||||
|
reloadCallback func([]*client.Client, *config.Config)
|
||||||
|
watcher *fsnotify.Watcher
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher creates a new file watcher instance
|
||||||
|
func NewWatcher(configPath, authDir string, reloadCallback func([]*client.Client, *config.Config)) (*Watcher, error) {
|
||||||
|
watcher, errNewWatcher := fsnotify.NewWatcher()
|
||||||
|
if errNewWatcher != nil {
|
||||||
|
return nil, errNewWatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Watcher{
|
||||||
|
configPath: configPath,
|
||||||
|
authDir: authDir,
|
||||||
|
reloadCallback: reloadCallback,
|
||||||
|
watcher: watcher,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start begins watching the configuration file and authentication directory
|
||||||
|
func (w *Watcher) Start(ctx context.Context) error {
|
||||||
|
// Watch the config file
|
||||||
|
if errAddConfig := w.watcher.Add(w.configPath); errAddConfig != nil {
|
||||||
|
log.Errorf("failed to watch config file %s: %v", w.configPath, errAddConfig)
|
||||||
|
return errAddConfig
|
||||||
|
}
|
||||||
|
log.Debugf("watching config file: %s", w.configPath)
|
||||||
|
|
||||||
|
// Watch the auth directory
|
||||||
|
if errAddAuthDir := w.watcher.Add(w.authDir); errAddAuthDir != nil {
|
||||||
|
log.Errorf("failed to watch auth directory %s: %v", w.authDir, errAddAuthDir)
|
||||||
|
return errAddAuthDir
|
||||||
|
}
|
||||||
|
log.Debugf("watching auth directory: %s", w.authDir)
|
||||||
|
|
||||||
|
// Start the event processing goroutine
|
||||||
|
go w.processEvents(ctx)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the file watcher
|
||||||
|
func (w *Watcher) Stop() error {
|
||||||
|
return w.watcher.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfig updates the current configuration
|
||||||
|
func (w *Watcher) SetConfig(cfg *config.Config) {
|
||||||
|
w.clientsMutex.Lock()
|
||||||
|
defer w.clientsMutex.Unlock()
|
||||||
|
w.config = cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClients updates the current client list
|
||||||
|
func (w *Watcher) SetClients(clients []*client.Client) {
|
||||||
|
w.clientsMutex.Lock()
|
||||||
|
defer w.clientsMutex.Unlock()
|
||||||
|
w.clients = clients
|
||||||
|
}
|
||||||
|
|
||||||
|
// processEvents handles file system events
|
||||||
|
func (w *Watcher) processEvents(ctx context.Context) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case event, ok := <-w.watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.handleEvent(event)
|
||||||
|
case errWatch, ok := <-w.watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Errorf("file watcher error: %v", errWatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleEvent processes individual file system events
|
||||||
|
func (w *Watcher) handleEvent(event fsnotify.Event) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
log.Debugf("file system event detected: %s %s", event.Op.String(), event.Name)
|
||||||
|
|
||||||
|
// Handle config file changes
|
||||||
|
if event.Name == w.configPath && (event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create) {
|
||||||
|
log.Infof("config file changed, reloading: %s", w.configPath)
|
||||||
|
log.Debugf("config file change details - operation: %s, timestamp: %s", event.Op.String(), now.Format("2006-01-02 15:04:05.000"))
|
||||||
|
w.reloadConfig()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle auth directory changes (only for .json files)
|
||||||
|
// Simplified: reload on any change to .json files in auth directory
|
||||||
|
if strings.HasPrefix(event.Name, w.authDir) && strings.HasSuffix(event.Name, ".json") {
|
||||||
|
log.Infof("auth file changed (%s): %s, reloading clients", event.Op.String(), filepath.Base(event.Name))
|
||||||
|
log.Debugf("auth file change details - operation: %s, file: %s, timestamp: %s",
|
||||||
|
event.Op.String(), filepath.Base(event.Name), now.Format("2006-01-02 15:04:05.000"))
|
||||||
|
w.reloadClients()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reloadConfig reloads the configuration and triggers a full reload
|
||||||
|
func (w *Watcher) reloadConfig() {
|
||||||
|
log.Debugf("starting config reload from: %s", w.configPath)
|
||||||
|
|
||||||
|
newConfig, errLoadConfig := config.LoadConfig(w.configPath)
|
||||||
|
if errLoadConfig != nil {
|
||||||
|
log.Errorf("failed to reload config: %v", errLoadConfig)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.clientsMutex.Lock()
|
||||||
|
oldConfig := w.config
|
||||||
|
w.config = newConfig
|
||||||
|
w.clientsMutex.Unlock()
|
||||||
|
|
||||||
|
// Log configuration changes in debug mode
|
||||||
|
if oldConfig != nil {
|
||||||
|
log.Debugf("config changes detected:")
|
||||||
|
if oldConfig.Port != newConfig.Port {
|
||||||
|
log.Debugf(" port: %d -> %d", oldConfig.Port, newConfig.Port)
|
||||||
|
}
|
||||||
|
if oldConfig.AuthDir != newConfig.AuthDir {
|
||||||
|
log.Debugf(" auth-dir: %s -> %s", oldConfig.AuthDir, newConfig.AuthDir)
|
||||||
|
}
|
||||||
|
if oldConfig.Debug != newConfig.Debug {
|
||||||
|
log.Debugf(" debug: %t -> %t", oldConfig.Debug, newConfig.Debug)
|
||||||
|
}
|
||||||
|
if oldConfig.ProxyUrl != newConfig.ProxyUrl {
|
||||||
|
log.Debugf(" proxy-url: %s -> %s", oldConfig.ProxyUrl, newConfig.ProxyUrl)
|
||||||
|
}
|
||||||
|
if len(oldConfig.ApiKeys) != len(newConfig.ApiKeys) {
|
||||||
|
log.Debugf(" api-keys count: %d -> %d", len(oldConfig.ApiKeys), len(newConfig.ApiKeys))
|
||||||
|
}
|
||||||
|
if len(oldConfig.GlAPIKey) != len(newConfig.GlAPIKey) {
|
||||||
|
log.Debugf(" generative-language-api-key count: %d -> %d", len(oldConfig.GlAPIKey), len(newConfig.GlAPIKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("config successfully reloaded, triggering client reload")
|
||||||
|
// Reload clients with new config
|
||||||
|
w.reloadClients()
|
||||||
|
}
|
||||||
|
|
||||||
|
// reloadClients reloads all authentication clients
|
||||||
|
func (w *Watcher) reloadClients() {
|
||||||
|
log.Debugf("starting client reload process")
|
||||||
|
|
||||||
|
w.clientsMutex.RLock()
|
||||||
|
cfg := w.config
|
||||||
|
oldClientCount := len(w.clients)
|
||||||
|
w.clientsMutex.RUnlock()
|
||||||
|
|
||||||
|
if cfg == nil {
|
||||||
|
log.Error("config is nil, cannot reload clients")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("scanning auth directory: %s", cfg.AuthDir)
|
||||||
|
|
||||||
|
// Create new client list
|
||||||
|
newClients := make([]*client.Client, 0)
|
||||||
|
authFileCount := 0
|
||||||
|
successfulAuthCount := 0
|
||||||
|
|
||||||
|
// Load clients from auth directory
|
||||||
|
errWalk := filepath.Walk(cfg.AuthDir, func(path string, info fs.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("error accessing path %s: %v", path, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process only JSON files in the auth directory
|
||||||
|
if !info.IsDir() && strings.HasSuffix(info.Name(), ".json") {
|
||||||
|
authFileCount++
|
||||||
|
log.Debugf("processing auth file %d: %s", authFileCount, filepath.Base(path))
|
||||||
|
|
||||||
|
f, errOpen := os.Open(path)
|
||||||
|
if errOpen != nil {
|
||||||
|
log.Errorf("failed to open token file %s: %v", path, errOpen)
|
||||||
|
return nil // Continue processing other files
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
errClose := f.Close()
|
||||||
|
if errClose != nil {
|
||||||
|
log.Errorf("failed to close token file %s: %v", path, errClose)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Decode the token storage file
|
||||||
|
var ts auth.TokenStorage
|
||||||
|
if errDecode := json.NewDecoder(f).Decode(&ts); errDecode == nil {
|
||||||
|
// For each valid token, create an authenticated client
|
||||||
|
clientCtx := context.Background()
|
||||||
|
log.Debugf(" initializing authentication for token from %s...", filepath.Base(path))
|
||||||
|
httpClient, errGetClient := auth.GetAuthenticatedClient(clientCtx, &ts, cfg)
|
||||||
|
if errGetClient != nil {
|
||||||
|
log.Errorf(" failed to get authenticated client for token %s: %v", path, errGetClient)
|
||||||
|
return nil // Continue processing other files
|
||||||
|
}
|
||||||
|
log.Debugf(" authentication successful for token from %s", filepath.Base(path))
|
||||||
|
|
||||||
|
// Add the new client to the pool
|
||||||
|
cliClient := client.NewClient(httpClient, &ts, cfg)
|
||||||
|
newClients = append(newClients, cliClient)
|
||||||
|
successfulAuthCount++
|
||||||
|
} else {
|
||||||
|
log.Errorf(" failed to decode token file %s: %v", path, errDecode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if errWalk != nil {
|
||||||
|
log.Errorf("error walking auth directory: %v", errWalk)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("auth directory scan complete - found %d .json files, %d successful authentications", authFileCount, successfulAuthCount)
|
||||||
|
|
||||||
|
// Add clients for Generative Language API keys if configured
|
||||||
|
glApiKeyCount := 0
|
||||||
|
if len(cfg.GlAPIKey) > 0 {
|
||||||
|
log.Debugf("processing %d Generative Language API keys", len(cfg.GlAPIKey))
|
||||||
|
for i := 0; i < len(cfg.GlAPIKey); i++ {
|
||||||
|
httpClient, errSetProxy := util.SetProxy(cfg, &http.Client{})
|
||||||
|
if errSetProxy != nil {
|
||||||
|
log.Errorf("set proxy failed for GL API key %d: %v", i+1, errSetProxy)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf(" initializing with Generative Language API key %d...", i+1)
|
||||||
|
cliClient := client.NewClient(httpClient, nil, cfg, cfg.GlAPIKey[i])
|
||||||
|
newClients = append(newClients, cliClient)
|
||||||
|
glApiKeyCount++
|
||||||
|
}
|
||||||
|
log.Debugf("successfully initialized %d Generative Language API key clients", glApiKeyCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the client list
|
||||||
|
w.clientsMutex.Lock()
|
||||||
|
w.clients = newClients
|
||||||
|
w.clientsMutex.Unlock()
|
||||||
|
|
||||||
|
log.Infof("client reload complete - old: %d clients, new: %d clients (%d auth files + %d GL API keys)",
|
||||||
|
oldClientCount, len(newClients), successfulAuthCount, glApiKeyCount)
|
||||||
|
|
||||||
|
// Trigger the callback to update the server
|
||||||
|
if w.reloadCallback != nil {
|
||||||
|
log.Debugf("triggering server update callback")
|
||||||
|
w.reloadCallback(newClients, cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user