Merge pull request #70 from router-for-me/log
Fix for the bug causing configuration to fail, and avoidance of invalid scanning of auth files.
This commit is contained in:
+4
-17
@@ -8,7 +8,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/cmd"
|
"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/config"
|
||||||
@@ -123,22 +122,10 @@ func main() {
|
|||||||
// Set the log level based on the configuration.
|
// Set the log level based on the configuration.
|
||||||
util.SetLogLevel(cfg)
|
util.SetLogLevel(cfg)
|
||||||
|
|
||||||
// Expand the tilde (~) in the auth directory path to the user's home directory.
|
if resolvedAuthDir, errResolveAuthDir := util.ResolveAuthDir(cfg.AuthDir); errResolveAuthDir != nil {
|
||||||
if strings.HasPrefix(cfg.AuthDir, "~") {
|
log.Fatalf("failed to resolve auth directory: %v", errResolveAuthDir)
|
||||||
home, errUserHomeDir := os.UserHomeDir()
|
} else {
|
||||||
if errUserHomeDir != nil {
|
cfg.AuthDir = resolvedAuthDir
|
||||||
log.Fatalf("failed to get home directory: %v", errUserHomeDir)
|
|
||||||
}
|
|
||||||
// Reconstruct the path by replacing the tilde with the user's home directory.
|
|
||||||
remainder := strings.TrimPrefix(cfg.AuthDir, "~")
|
|
||||||
remainder = strings.TrimLeft(remainder, "/\\")
|
|
||||||
if remainder == "" {
|
|
||||||
cfg.AuthDir = home
|
|
||||||
} else {
|
|
||||||
// Normalize any slash style in the remainder so Windows paths keep nested directories.
|
|
||||||
normalized := strings.ReplaceAll(remainder, "\\", "/")
|
|
||||||
cfg.AuthDir = filepath.Join(home, filepath.FromSlash(normalized))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create login options to be used in authentication flows.
|
// Create login options to be used in authentication flows.
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
package access
|
package access
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/access"
|
sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access"
|
||||||
sdkConfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
sdkConfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReconcileProviders builds the desired provider list by reusing existing providers when possible
|
// ReconcileProviders builds the desired provider list by reusing existing providers when possible
|
||||||
// and creating or removing providers only when their configuration changed. It returns the final
|
// and creating or removing providers only when their configuration changed. It returns the final
|
||||||
// ordered provider slice along with the identifiers of providers that were added, updated, or
|
// ordered provider slice along with the identifiers of providers that were added, updated, or
|
||||||
// removed compared to the previous configuration.
|
// removed compared to the previous configuration.
|
||||||
func ReconcileProviders(oldCfg, newCfg *config.Config, existing []access.Provider) (result []access.Provider, added, updated, removed []string, err error) {
|
func ReconcileProviders(oldCfg, newCfg *config.Config, existing []sdkaccess.Provider) (result []sdkaccess.Provider, added, updated, removed []string, err error) {
|
||||||
if newCfg == nil {
|
if newCfg == nil {
|
||||||
return nil, nil, nil, nil, nil
|
return nil, nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
existingMap := make(map[string]access.Provider, len(existing))
|
existingMap := make(map[string]sdkaccess.Provider, len(existing))
|
||||||
for _, provider := range existing {
|
for _, provider := range existing {
|
||||||
if provider == nil {
|
if provider == nil {
|
||||||
continue
|
continue
|
||||||
@@ -30,7 +32,7 @@ func ReconcileProviders(oldCfg, newCfg *config.Config, existing []access.Provide
|
|||||||
oldCfgMap := accessProviderMap(oldCfg)
|
oldCfgMap := accessProviderMap(oldCfg)
|
||||||
newEntries := collectProviderEntries(newCfg)
|
newEntries := collectProviderEntries(newCfg)
|
||||||
|
|
||||||
result = make([]access.Provider, 0, len(newEntries))
|
result = make([]sdkaccess.Provider, 0, len(newEntries))
|
||||||
finalIDs := make(map[string]struct{}, len(newEntries))
|
finalIDs := make(map[string]struct{}, len(newEntries))
|
||||||
|
|
||||||
isInlineProvider := func(id string) bool {
|
isInlineProvider := func(id string) bool {
|
||||||
@@ -60,7 +62,7 @@ func ReconcileProviders(oldCfg, newCfg *config.Config, existing []access.Provide
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, buildErr := access.BuildProvider(providerCfg, &newCfg.SDKConfig)
|
provider, buildErr := sdkaccess.BuildProvider(providerCfg, &newCfg.SDKConfig)
|
||||||
if buildErr != nil {
|
if buildErr != nil {
|
||||||
return nil, nil, nil, nil, buildErr
|
return nil, nil, nil, nil, buildErr
|
||||||
}
|
}
|
||||||
@@ -88,7 +90,7 @@ func ReconcileProviders(oldCfg, newCfg *config.Config, existing []access.Provide
|
|||||||
if existingProvider, okExisting := existingMap[key]; okExisting {
|
if existingProvider, okExisting := existingMap[key]; okExisting {
|
||||||
result = append(result, existingProvider)
|
result = append(result, existingProvider)
|
||||||
} else {
|
} else {
|
||||||
provider, buildErr := access.BuildProvider(providerCfg, &newCfg.SDKConfig)
|
provider, buildErr := sdkaccess.BuildProvider(providerCfg, &newCfg.SDKConfig)
|
||||||
if buildErr != nil {
|
if buildErr != nil {
|
||||||
return nil, nil, nil, nil, buildErr
|
return nil, nil, nil, nil, buildErr
|
||||||
}
|
}
|
||||||
@@ -100,7 +102,7 @@ func ReconcileProviders(oldCfg, newCfg *config.Config, existing []access.Provide
|
|||||||
result = append(result, provider)
|
result = append(result, provider)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
provider, buildErr := access.BuildProvider(providerCfg, &newCfg.SDKConfig)
|
provider, buildErr := sdkaccess.BuildProvider(providerCfg, &newCfg.SDKConfig)
|
||||||
if buildErr != nil {
|
if buildErr != nil {
|
||||||
return nil, nil, nil, nil, buildErr
|
return nil, nil, nil, nil, buildErr
|
||||||
}
|
}
|
||||||
@@ -112,7 +114,7 @@ func ReconcileProviders(oldCfg, newCfg *config.Config, existing []access.Provide
|
|||||||
result = append(result, provider)
|
result = append(result, provider)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
provider, buildErr := access.BuildProvider(providerCfg, &newCfg.SDKConfig)
|
provider, buildErr := sdkaccess.BuildProvider(providerCfg, &newCfg.SDKConfig)
|
||||||
if buildErr != nil {
|
if buildErr != nil {
|
||||||
return nil, nil, nil, nil, buildErr
|
return nil, nil, nil, nil, buildErr
|
||||||
}
|
}
|
||||||
@@ -146,6 +148,33 @@ func ReconcileProviders(oldCfg, newCfg *config.Config, existing []access.Provide
|
|||||||
return result, added, updated, removed, nil
|
return result, added, updated, removed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplyAccessProviders reconciles the configured access providers against the
|
||||||
|
// currently registered providers and updates the manager. It logs a concise
|
||||||
|
// summary of the detected changes and returns whether any provider changed.
|
||||||
|
func ApplyAccessProviders(manager *sdkaccess.Manager, oldCfg, newCfg *config.Config) (bool, error) {
|
||||||
|
if manager == nil || newCfg == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
existing := manager.Providers()
|
||||||
|
providers, added, updated, removed, err := ReconcileProviders(oldCfg, newCfg, existing)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to reconcile request auth providers: %v", err)
|
||||||
|
return false, fmt.Errorf("reconciling access providers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.SetProviders(providers)
|
||||||
|
|
||||||
|
if len(added)+len(updated)+len(removed) > 0 {
|
||||||
|
log.Debugf("auth providers reconciled (added=%d updated=%d removed=%d)", len(added), len(updated), len(removed))
|
||||||
|
log.Debugf("auth provider changes details - added=%v updated=%v removed=%v", added, updated, removed)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.Debug("auth providers unchanged after config update")
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func accessProviderMap(cfg *config.Config) map[string]*sdkConfig.AccessProvider {
|
func accessProviderMap(cfg *config.Config) map[string]*sdkConfig.AccessProvider {
|
||||||
result := make(map[string]*sdkConfig.AccessProvider)
|
result := make(map[string]*sdkConfig.AccessProvider)
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
|
|||||||
+1
-11
@@ -552,19 +552,9 @@ func (s *Server) applyAccessConfig(oldCfg, newCfg *config.Config) {
|
|||||||
if s == nil || s.accessManager == nil || newCfg == nil {
|
if s == nil || s.accessManager == nil || newCfg == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
existing := s.accessManager.Providers()
|
if _, err := access.ApplyAccessProviders(s.accessManager, oldCfg, newCfg); err != nil {
|
||||||
providers, added, updated, removed, err := access.ReconcileProviders(oldCfg, newCfg, existing)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to reconcile request auth providers: %v", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.accessManager.SetProviders(providers)
|
|
||||||
if len(added)+len(updated)+len(removed) > 0 {
|
|
||||||
log.Debugf("auth providers reconciled (added=%d updated=%d removed=%d)", len(added), len(updated), len(removed))
|
|
||||||
log.Debugf("auth provider changes details - added=%v updated=%v removed=%v", added, updated, removed)
|
|
||||||
} else {
|
|
||||||
log.Debug("auth providers unchanged after config update")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateClients updates the server's client list and configuration.
|
// UpdateClients updates the server's client list and configuration.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
// Config represents the application's configuration, loaded from a YAML file.
|
// Config represents the application's configuration, loaded from a YAML file.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
config.SDKConfig
|
config.SDKConfig `yaml:",inline"`
|
||||||
// Port is the network port on which the API server will listen.
|
// Port is the network port on which the API server will listen.
|
||||||
Port int `yaml:"port" json:"-"`
|
Port int `yaml:"port" json:"-"`
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ func (s *GeminiWebState) Label() string {
|
|||||||
if s == nil {
|
if s == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
if s.token != nil {
|
||||||
|
if lbl := strings.TrimSpace(s.token.Label); lbl != "" {
|
||||||
|
return lbl
|
||||||
|
}
|
||||||
|
}
|
||||||
if s.storagePath != "" {
|
if s.storagePath != "" {
|
||||||
base := strings.TrimSuffix(filepath.Base(s.storagePath), filepath.Ext(s.storagePath))
|
base := strings.TrimSuffix(filepath.Base(s.storagePath), filepath.Ext(s.storagePath))
|
||||||
if base != "" {
|
if base != "" {
|
||||||
@@ -169,8 +174,12 @@ func (s *GeminiWebState) Refresh(ctx context.Context) error {
|
|||||||
s.client.Cookies["__Secure-1PSIDTS"] = newTS
|
s.client.Cookies["__Secure-1PSIDTS"] = newTS
|
||||||
}
|
}
|
||||||
s.tokenMu.Unlock()
|
s.tokenMu.Unlock()
|
||||||
// Detailed debug log: provider and account.
|
// Detailed debug log: provider and account label.
|
||||||
log.Debugf("gemini web account %s rotated 1PSIDTS: %s", s.accountID, MaskToken28(newTS))
|
label := strings.TrimSpace(s.Label())
|
||||||
|
if label == "" {
|
||||||
|
label = s.accountID
|
||||||
|
}
|
||||||
|
log.Debugf("gemini web account %s rotated 1PSIDTS: %s", label, MaskToken28(newTS))
|
||||||
}
|
}
|
||||||
s.lastRefresh = time.Now()
|
s.lastRefresh = time.Now()
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -200,7 +200,8 @@ func parseGeminiWebToken(auth *cliproxyauth.Auth) (*gemini.GeminiWebTokenStorage
|
|||||||
if psid == "" || psidts == "" {
|
if psid == "" || psidts == "" {
|
||||||
return nil, fmt.Errorf("gemini-web executor: incomplete cookie metadata")
|
return nil, fmt.Errorf("gemini-web executor: incomplete cookie metadata")
|
||||||
}
|
}
|
||||||
return &gemini.GeminiWebTokenStorage{Secure1PSID: psid, Secure1PSIDTS: psidts}, nil
|
label := strings.TrimSpace(stringFromMetadata(auth.Metadata, "label"))
|
||||||
|
return &gemini.GeminiWebTokenStorage{Secure1PSID: psid, Secure1PSIDTS: psidts, Label: label}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringFromMetadata(meta map[string]any, keys ...string) string {
|
func stringFromMetadata(meta map[string]any, keys ...string) string {
|
||||||
|
|||||||
+29
-9
@@ -4,6 +4,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -30,23 +31,42 @@ func SetLogLevel(cfg *config.Config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountAuthFiles returns the number of JSON auth files located under the provided directory.
|
// ResolveAuthDir normalizes the auth directory path for consistent reuse throughout the app.
|
||||||
// The function resolves leading tildes to the user's home directory and performs a case-insensitive
|
// It expands a leading tilde (~) to the user's home directory and returns a cleaned path.
|
||||||
// match on the ".json" suffix so that files saved with uppercase extensions are also counted.
|
func ResolveAuthDir(authDir string) (string, error) {
|
||||||
func CountAuthFiles(authDir string) int {
|
|
||||||
if authDir == "" {
|
if authDir == "" {
|
||||||
return 0
|
return "", nil
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(authDir, "~") {
|
if strings.HasPrefix(authDir, "~") {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("countAuthFiles: failed to resolve home directory: %v", err)
|
return "", fmt.Errorf("resolve auth dir: %w", err)
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
authDir = filepath.Join(home, authDir[1:])
|
remainder := strings.TrimPrefix(authDir, "~")
|
||||||
|
remainder = strings.TrimLeft(remainder, "/\\")
|
||||||
|
if remainder == "" {
|
||||||
|
return filepath.Clean(home), nil
|
||||||
|
}
|
||||||
|
normalized := strings.ReplaceAll(remainder, "\\", "/")
|
||||||
|
return filepath.Clean(filepath.Join(home, filepath.FromSlash(normalized))), nil
|
||||||
|
}
|
||||||
|
return filepath.Clean(authDir), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountAuthFiles returns the number of JSON auth files located under the provided directory.
|
||||||
|
// The function resolves leading tildes to the user's home directory and performs a case-insensitive
|
||||||
|
// match on the ".json" suffix so that files saved with uppercase extensions are also counted.
|
||||||
|
func CountAuthFiles(authDir string) int {
|
||||||
|
dir, err := ResolveAuthDir(authDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("countAuthFiles: failed to resolve auth directory: %v", err)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if dir == "" {
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
count := 0
|
count := 0
|
||||||
walkErr := filepath.WalkDir(authDir, func(path string, d fs.DirEntry, err error) error {
|
walkErr := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("countAuthFiles: error accessing %s: %v", path, err)
|
log.Debugf("countAuthFiles: error accessing %s: %v", path, err)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+171
-31
@@ -14,6 +14,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -145,7 +146,7 @@ func (w *Watcher) Start(ctx context.Context) error {
|
|||||||
go w.processEvents(ctx)
|
go w.processEvents(ctx)
|
||||||
|
|
||||||
// Perform an initial full reload based on current config and auth dir
|
// Perform an initial full reload based on current config and auth dir
|
||||||
w.reloadClients()
|
w.reloadClients(true)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,6 +464,12 @@ func (w *Watcher) reloadConfig() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if resolvedAuthDir, errResolveAuthDir := util.ResolveAuthDir(newConfig.AuthDir); errResolveAuthDir != nil {
|
||||||
|
log.Errorf("failed to resolve auth directory from config: %v", errResolveAuthDir)
|
||||||
|
} else {
|
||||||
|
newConfig.AuthDir = resolvedAuthDir
|
||||||
|
}
|
||||||
|
|
||||||
w.clientsMutex.Lock()
|
w.clientsMutex.Lock()
|
||||||
oldConfig := w.config
|
oldConfig := w.config
|
||||||
w.config = newConfig
|
w.config = newConfig
|
||||||
@@ -530,16 +537,24 @@ func (w *Watcher) reloadConfig() bool {
|
|||||||
if oldConfig.UsageStatisticsEnabled != newConfig.UsageStatisticsEnabled {
|
if oldConfig.UsageStatisticsEnabled != newConfig.UsageStatisticsEnabled {
|
||||||
log.Debugf(" usage-statistics-enabled: %t -> %t", oldConfig.UsageStatisticsEnabled, newConfig.UsageStatisticsEnabled)
|
log.Debugf(" usage-statistics-enabled: %t -> %t", oldConfig.UsageStatisticsEnabled, newConfig.UsageStatisticsEnabled)
|
||||||
}
|
}
|
||||||
|
if changes := diffOpenAICompatibility(oldConfig.OpenAICompatibility, newConfig.OpenAICompatibility); len(changes) > 0 {
|
||||||
|
log.Debugf(" openai-compatibility:")
|
||||||
|
for _, change := range changes {
|
||||||
|
log.Debugf(" %s", change)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authDirChanged := oldConfig == nil || oldConfig.AuthDir != newConfig.AuthDir
|
||||||
|
|
||||||
log.Infof("config successfully reloaded, triggering client reload")
|
log.Infof("config successfully reloaded, triggering client reload")
|
||||||
// Reload clients with new config
|
// Reload clients with new config
|
||||||
w.reloadClients()
|
w.reloadClients(authDirChanged)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// reloadClients performs a full scan and reload of all clients.
|
// reloadClients performs a full scan and reload of all clients.
|
||||||
func (w *Watcher) reloadClients() {
|
func (w *Watcher) reloadClients(rescanAuth bool) {
|
||||||
log.Debugf("starting full client reload process")
|
log.Debugf("starting full client reload process")
|
||||||
|
|
||||||
w.clientsMutex.RLock()
|
w.clientsMutex.RLock()
|
||||||
@@ -556,33 +571,48 @@ func (w *Watcher) reloadClients() {
|
|||||||
|
|
||||||
// Create new API key clients based on the new config
|
// Create new API key clients based on the new config
|
||||||
glAPIKeyCount, claudeAPIKeyCount, codexAPIKeyCount, openAICompatCount := BuildAPIKeyClients(cfg)
|
glAPIKeyCount, claudeAPIKeyCount, codexAPIKeyCount, openAICompatCount := BuildAPIKeyClients(cfg)
|
||||||
log.Debugf("created %d new API key clients", 0)
|
totalAPIKeyClients := glAPIKeyCount + claudeAPIKeyCount + codexAPIKeyCount + openAICompatCount
|
||||||
|
log.Debugf("loaded %d API key clients", totalAPIKeyClients)
|
||||||
|
|
||||||
// Load file-based clients
|
var authFileCount int
|
||||||
authFileCount := w.loadFileClients(cfg)
|
if rescanAuth {
|
||||||
log.Debugf("loaded %d new file-based clients", 0)
|
// Load file-based clients when explicitly requested (startup or authDir change)
|
||||||
|
authFileCount = w.loadFileClients(cfg)
|
||||||
|
log.Debugf("loaded %d new file-based clients", authFileCount)
|
||||||
|
} else {
|
||||||
|
// Preserve existing auth hashes and only report current known count to avoid redundant scans.
|
||||||
|
w.clientsMutex.RLock()
|
||||||
|
authFileCount = len(w.lastAuthHashes)
|
||||||
|
w.clientsMutex.RUnlock()
|
||||||
|
log.Debugf("skipping auth directory rescan; retaining %d existing auth files", authFileCount)
|
||||||
|
}
|
||||||
|
|
||||||
// no legacy file-based clients to unregister
|
// no legacy file-based clients to unregister
|
||||||
|
|
||||||
// Update client maps
|
// Update client maps
|
||||||
w.clientsMutex.Lock()
|
if rescanAuth {
|
||||||
|
w.clientsMutex.Lock()
|
||||||
|
|
||||||
// Rebuild auth file hash cache for current clients
|
// Rebuild auth file hash cache for current clients
|
||||||
w.lastAuthHashes = make(map[string]string)
|
w.lastAuthHashes = make(map[string]string)
|
||||||
// Recompute hashes for current auth files
|
if resolvedAuthDir, errResolveAuthDir := util.ResolveAuthDir(cfg.AuthDir); errResolveAuthDir != nil {
|
||||||
_ = filepath.Walk(cfg.AuthDir, func(path string, info fs.FileInfo, err error) error {
|
log.Errorf("failed to resolve auth directory for hash cache: %v", errResolveAuthDir)
|
||||||
if err != nil {
|
} else if resolvedAuthDir != "" {
|
||||||
return nil
|
_ = filepath.Walk(resolvedAuthDir, func(path string, info fs.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".json") {
|
||||||
|
if data, errReadFile := os.ReadFile(path); errReadFile == nil && len(data) > 0 {
|
||||||
|
sum := sha256.Sum256(data)
|
||||||
|
w.lastAuthHashes[path] = hex.EncodeToString(sum[:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".json") {
|
w.clientsMutex.Unlock()
|
||||||
if data, errReadFile := os.ReadFile(path); errReadFile == nil && len(data) > 0 {
|
}
|
||||||
sum := sha256.Sum256(data)
|
|
||||||
w.lastAuthHashes[path] = hex.EncodeToString(sum[:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
w.clientsMutex.Unlock()
|
|
||||||
|
|
||||||
totalNewClients := authFileCount + glAPIKeyCount + claudeAPIKeyCount + codexAPIKeyCount + openAICompatCount
|
totalNewClients := authFileCount + glAPIKeyCount + claudeAPIKeyCount + codexAPIKeyCount + openAICompatCount
|
||||||
|
|
||||||
@@ -855,14 +885,13 @@ func (w *Watcher) loadFileClients(cfg *config.Config) int {
|
|||||||
authFileCount := 0
|
authFileCount := 0
|
||||||
successfulAuthCount := 0
|
successfulAuthCount := 0
|
||||||
|
|
||||||
authDir := cfg.AuthDir
|
authDir, errResolveAuthDir := util.ResolveAuthDir(cfg.AuthDir)
|
||||||
if strings.HasPrefix(authDir, "~") {
|
if errResolveAuthDir != nil {
|
||||||
home, err := os.UserHomeDir()
|
log.Errorf("failed to resolve auth directory: %v", errResolveAuthDir)
|
||||||
if err != nil {
|
return 0
|
||||||
log.Errorf("failed to get home directory: %v", err)
|
}
|
||||||
return 0
|
if authDir == "" {
|
||||||
}
|
return 0
|
||||||
authDir = filepath.Join(home, authDir[1:])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errWalk := filepath.Walk(authDir, func(path string, info fs.FileInfo, err error) error {
|
errWalk := filepath.Walk(authDir, func(path string, info fs.FileInfo, err error) error {
|
||||||
@@ -912,3 +941,114 @@ func BuildAPIKeyClients(cfg *config.Config) (int, int, int, int) {
|
|||||||
}
|
}
|
||||||
return glAPIKeyCount, claudeAPIKeyCount, codexAPIKeyCount, openAICompatCount
|
return glAPIKeyCount, claudeAPIKeyCount, codexAPIKeyCount, openAICompatCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func diffOpenAICompatibility(oldList, newList []config.OpenAICompatibility) []string {
|
||||||
|
changes := make([]string, 0)
|
||||||
|
oldMap := make(map[string]config.OpenAICompatibility, len(oldList))
|
||||||
|
oldLabels := make(map[string]string, len(oldList))
|
||||||
|
for idx, entry := range oldList {
|
||||||
|
key, label := openAICompatKey(entry, idx)
|
||||||
|
oldMap[key] = entry
|
||||||
|
oldLabels[key] = label
|
||||||
|
}
|
||||||
|
newMap := make(map[string]config.OpenAICompatibility, len(newList))
|
||||||
|
newLabels := make(map[string]string, len(newList))
|
||||||
|
for idx, entry := range newList {
|
||||||
|
key, label := openAICompatKey(entry, idx)
|
||||||
|
newMap[key] = entry
|
||||||
|
newLabels[key] = label
|
||||||
|
}
|
||||||
|
keySet := make(map[string]struct{}, len(oldMap)+len(newMap))
|
||||||
|
for key := range oldMap {
|
||||||
|
keySet[key] = struct{}{}
|
||||||
|
}
|
||||||
|
for key := range newMap {
|
||||||
|
keySet[key] = struct{}{}
|
||||||
|
}
|
||||||
|
orderedKeys := make([]string, 0, len(keySet))
|
||||||
|
for key := range keySet {
|
||||||
|
orderedKeys = append(orderedKeys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(orderedKeys)
|
||||||
|
for _, key := range orderedKeys {
|
||||||
|
oldEntry, oldOk := oldMap[key]
|
||||||
|
newEntry, newOk := newMap[key]
|
||||||
|
label := oldLabels[key]
|
||||||
|
if label == "" {
|
||||||
|
label = newLabels[key]
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case !oldOk:
|
||||||
|
changes = append(changes, fmt.Sprintf("provider added: %s (api-keys=%d, models=%d)", label, countNonEmptyStrings(newEntry.APIKeys), countOpenAIModels(newEntry.Models)))
|
||||||
|
case !newOk:
|
||||||
|
changes = append(changes, fmt.Sprintf("provider removed: %s (api-keys=%d, models=%d)", label, countNonEmptyStrings(oldEntry.APIKeys), countOpenAIModels(oldEntry.Models)))
|
||||||
|
default:
|
||||||
|
if detail := describeOpenAICompatibilityUpdate(oldEntry, newEntry); detail != "" {
|
||||||
|
changes = append(changes, fmt.Sprintf("provider updated: %s %s", label, detail))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
func describeOpenAICompatibilityUpdate(oldEntry, newEntry config.OpenAICompatibility) string {
|
||||||
|
oldKeyCount := countNonEmptyStrings(oldEntry.APIKeys)
|
||||||
|
newKeyCount := countNonEmptyStrings(newEntry.APIKeys)
|
||||||
|
oldModelCount := countOpenAIModels(oldEntry.Models)
|
||||||
|
newModelCount := countOpenAIModels(newEntry.Models)
|
||||||
|
details := make([]string, 0, 2)
|
||||||
|
if oldKeyCount != newKeyCount {
|
||||||
|
details = append(details, fmt.Sprintf("api-keys %d -> %d", oldKeyCount, newKeyCount))
|
||||||
|
}
|
||||||
|
if oldModelCount != newModelCount {
|
||||||
|
details = append(details, fmt.Sprintf("models %d -> %d", oldModelCount, newModelCount))
|
||||||
|
}
|
||||||
|
if len(details) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "(" + strings.Join(details, ", ") + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
func countNonEmptyStrings(values []string) int {
|
||||||
|
count := 0
|
||||||
|
for _, value := range values {
|
||||||
|
if strings.TrimSpace(value) != "" {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func countOpenAIModels(models []config.OpenAICompatibilityModel) int {
|
||||||
|
count := 0
|
||||||
|
for _, model := range models {
|
||||||
|
name := strings.TrimSpace(model.Name)
|
||||||
|
alias := strings.TrimSpace(model.Alias)
|
||||||
|
if name == "" && alias == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func openAICompatKey(entry config.OpenAICompatibility, index int) (string, string) {
|
||||||
|
name := strings.TrimSpace(entry.Name)
|
||||||
|
if name != "" {
|
||||||
|
return "name:" + name, name
|
||||||
|
}
|
||||||
|
base := strings.TrimSpace(entry.BaseURL)
|
||||||
|
if base != "" {
|
||||||
|
return "base:" + base, base
|
||||||
|
}
|
||||||
|
for _, model := range entry.Models {
|
||||||
|
alias := strings.TrimSpace(model.Alias)
|
||||||
|
if alias == "" {
|
||||||
|
alias = strings.TrimSpace(model.Name)
|
||||||
|
}
|
||||||
|
if alias != "" {
|
||||||
|
return "alias:" + alias, alias
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("index:%d", index), fmt.Sprintf("entry-%d", index+1)
|
||||||
|
}
|
||||||
|
|||||||
+1
-11
@@ -115,19 +115,9 @@ func (s *Service) refreshAccessProviders(cfg *config.Config) {
|
|||||||
oldCfg := s.cfg
|
oldCfg := s.cfg
|
||||||
s.cfgMu.RUnlock()
|
s.cfgMu.RUnlock()
|
||||||
|
|
||||||
existing := s.accessManager.Providers()
|
if _, err := access.ApplyAccessProviders(s.accessManager, oldCfg, cfg); err != nil {
|
||||||
providers, added, updated, removed, err := access.ReconcileProviders(oldCfg, cfg, existing)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to reconcile request auth providers: %v", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.accessManager.SetProviders(providers)
|
|
||||||
if len(added)+len(updated)+len(removed) > 0 {
|
|
||||||
log.Debugf("auth providers reconciled (added=%d updated=%d removed=%d)", len(added), len(updated), len(removed))
|
|
||||||
log.Debugf("auth provider changes details - added=%v updated=%v removed=%v", added, updated, removed)
|
|
||||||
} else {
|
|
||||||
log.Debug("auth providers unchanged after config reload")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ensureAuthUpdateQueue(ctx context.Context) {
|
func (s *Service) ensureAuthUpdateQueue(ctx context.Context) {
|
||||||
|
|||||||
Reference in New Issue
Block a user