diff --git a/.gitignore b/.gitignore index a9bf4ab4..8397af66 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ temp/* cli-proxy-api static/* .env +pgstore/* +gitstore/* \ No newline at end of file diff --git a/README.md b/README.md index 7d62b888..d06bba27 100644 --- a/README.md +++ b/README.md @@ -447,12 +447,12 @@ You can also persist configuration and authentication data in PostgreSQL when ru | `MANAGEMENT_PASSWORD` | Yes | | Password for the management web UI (required when remote management is enabled). | | `PGSTORE_DSN` | Yes | | PostgreSQL connection string (e.g. `postgresql://user:pass@host:5432/db`). | | `PGSTORE_SCHEMA` | No | public | Schema where the tables will be created. Leave empty to use the default schema. | -| `PGSTORE_CACHE_DIR` | No | Current working directory | Root directory for the local mirror; the server writes to `/pgstore`. If unset and CWD is unavailable, `/tmp/pgstore` is used. | +| `PGSTORE_LOCAL_PATH` | No | Current working directory | Root directory for the local mirror; the server writes to `/pgstore`. If unset and CWD is unavailable, `/tmp/pgstore` is used. | **How it Works** 1. **Initialization:** On startup the server connects via `PGSTORE_DSN`, ensures the schema exists, and creates the `config_store` / `auth_store` tables when missing. -2. **Local Mirror:** A writable cache at `/pgstore` mirrors `config/config.yaml` and `auths/` so the rest of the application can reuse the existing file-based logic. +2. **Local Mirror:** A writable cache at `/pgstore` mirrors `config/config.yaml` and `auths/` so the rest of the application can reuse the existing file-based logic. 3. **Bootstrapping:** If no configuration row exists, `config.example.yaml` seeds the database using the fixed identifier `config`. 4. **Token Sync:** Changes flow both ways—file updates are written to PostgreSQL and database records are mirrored back to disk so watchers and management APIs continue to operate. diff --git a/README_CN.md b/README_CN.md index 10572a76..11e8dc7d 100644 --- a/README_CN.md +++ b/README_CN.md @@ -460,12 +460,12 @@ openai-compatibility: | `MANAGEMENT_PASSWORD` | 是 | | 管理面板密码(启用远程管理时必需)。 | | `PGSTORE_DSN` | 是 | | PostgreSQL 连接串,例如 `postgresql://user:pass@host:5432/db`。 | | `PGSTORE_SCHEMA` | 否 | public | 创建表时使用的 schema;留空则使用默认 schema。 | -| `PGSTORE_CACHE_DIR` | 否 | 当前工作目录 | 本地镜像根目录,服务将在 `<值>/pgstore` 下写入缓存;若无法获取工作目录则退回 `/tmp/pgstore`。 | +| `PGSTORE_LOCAL_PATH` | 否 | 当前工作目录 | 本地镜像根目录,服务将在 `<值>/pgstore` 下写入缓存;若无法获取工作目录则退回 `/tmp/pgstore`。 | **工作原理** 1. **初始化:** 启动时通过 `PGSTORE_DSN` 连接数据库,确保 schema 存在,并在缺失时创建 `config_store` 与 `auth_store`。 -2. **本地镜像:** 在 `/pgstore` 下建立可写缓存,复用 `config/config.yaml` 与 `auths/` 目录。 +2. **本地镜像:** 在 `/pgstore` 下建立可写缓存,复用 `config/config.yaml` 与 `auths/` 目录。 3. **引导:** 若数据库中无配置记录,会使用 `config.example.yaml` 初始化,并以固定标识 `config` 写入。 4. **令牌同步:** 配置与令牌的更改会写入 PostgreSQL,同时数据库中的内容也会反向同步至本地镜像,便于文件监听与管理接口继续工作。 diff --git a/cmd/server/main.go b/cmd/server/main.go index 77f7b29b..687f7f6b 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -105,7 +105,7 @@ func main() { usePostgresStore bool pgStoreDSN string pgStoreSchema string - pgStoreCacheDir string + pgStoreLocalPath string pgStoreInst *store.PostgresStore gitStoreLocalPath string useGitStore bool @@ -139,8 +139,8 @@ func main() { if value, ok := lookupEnv("PGSTORE_SCHEMA", "pgstore_schema"); ok { pgStoreSchema = value } - if value, ok := lookupEnv("PGSTORE_CACHE_DIR", "pgstore_cache_dir"); ok { - pgStoreCacheDir = value + if value, ok := lookupEnv("PGSTORE_LOCAL_PATH", "pgstore_local_path"); ok { + pgStoreLocalPath = value } useGitStore = false } @@ -169,15 +169,15 @@ func main() { // Prefer the Postgres store when configured, otherwise fallback to git or local files. var configFilePath string if usePostgresStore { - if pgStoreCacheDir == "" { - pgStoreCacheDir = wd + if pgStoreLocalPath == "" { + pgStoreLocalPath = wd } - pgStoreCacheDir = filepath.Join(pgStoreCacheDir, "pgstore") + 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: pgStoreCacheDir, + SpoolDir: pgStoreLocalPath, }) cancel() if err != nil { diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index 7f440624..640a3d2f 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -35,25 +35,30 @@ type storePersister interface { PersistAuthFiles(ctx context.Context, message string, paths ...string) error } +type authDirProvider interface { + AuthDir() string +} + // Watcher manages file watching for configuration and authentication files type Watcher struct { - configPath string - authDir string - config *config.Config - clientsMutex sync.RWMutex - reloadCallback func(*config.Config) - watcher *fsnotify.Watcher - lastAuthHashes map[string]string - lastConfigHash string - authQueue chan<- AuthUpdate - currentAuths map[string]*coreauth.Auth - dispatchMu sync.Mutex - dispatchCond *sync.Cond - pendingUpdates map[string]AuthUpdate - pendingOrder []string - dispatchCancel context.CancelFunc - storePersister storePersister - oldConfigYaml []byte + configPath string + authDir string + config *config.Config + clientsMutex sync.RWMutex + reloadCallback func(*config.Config) + watcher *fsnotify.Watcher + lastAuthHashes map[string]string + lastConfigHash string + authQueue chan<- AuthUpdate + currentAuths map[string]*coreauth.Auth + dispatchMu sync.Mutex + dispatchCond *sync.Cond + pendingUpdates map[string]AuthUpdate + pendingOrder []string + dispatchCancel context.CancelFunc + storePersister storePersister + mirroredAuthDir string + oldConfigYaml []byte } type stableIDGenerator struct { @@ -130,6 +135,12 @@ func NewWatcher(configPath, authDir string, reloadCallback func(*config.Config)) w.storePersister = persister log.Debug("persistence-capable token store detected; watcher will propagate persisted changes") } + if provider, ok := store.(authDirProvider); ok { + if fixed := strings.TrimSpace(provider.AuthDir()); fixed != "" { + w.mirroredAuthDir = fixed + log.Debugf("mirrored auth directory locked to %s", fixed) + } + } } return w, nil } @@ -517,10 +528,14 @@ func (w *Watcher) reloadConfig() bool { return false } - if resolvedAuthDir, errResolveAuthDir := util.ResolveAuthDir(newConfig.AuthDir); errResolveAuthDir != nil { - log.Errorf("failed to resolve auth directory from config: %v", errResolveAuthDir) + if w.mirroredAuthDir != "" { + newConfig.AuthDir = w.mirroredAuthDir } else { - newConfig.AuthDir = resolvedAuthDir + 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()