mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
feat(store)!: Lock AuthDir when use gitstore/pgstore
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,3 +16,5 @@ temp/*
|
|||||||
cli-proxy-api
|
cli-proxy-api
|
||||||
static/*
|
static/*
|
||||||
.env
|
.env
|
||||||
|
pgstore/*
|
||||||
|
gitstore/*
|
||||||
@@ -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). |
|
| `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_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_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 `<value>/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 `<value>/pgstore`. If unset and CWD is unavailable, `/tmp/pgstore` is used. |
|
||||||
|
|
||||||
**How it Works**
|
**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.
|
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_CACHE_DIR or CWD>/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_LOCAL_PATH or CWD>/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`.
|
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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -460,12 +460,12 @@ openai-compatibility:
|
|||||||
| `MANAGEMENT_PASSWORD` | 是 | | 管理面板密码(启用远程管理时必需)。 |
|
| `MANAGEMENT_PASSWORD` | 是 | | 管理面板密码(启用远程管理时必需)。 |
|
||||||
| `PGSTORE_DSN` | 是 | | PostgreSQL 连接串,例如 `postgresql://user:pass@host:5432/db`。 |
|
| `PGSTORE_DSN` | 是 | | PostgreSQL 连接串,例如 `postgresql://user:pass@host:5432/db`。 |
|
||||||
| `PGSTORE_SCHEMA` | 否 | public | 创建表时使用的 schema;留空则使用默认 schema。 |
|
| `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`。
|
1. **初始化:** 启动时通过 `PGSTORE_DSN` 连接数据库,确保 schema 存在,并在缺失时创建 `config_store` 与 `auth_store`。
|
||||||
2. **本地镜像:** 在 `<PGSTORE_CACHE_DIR 或当前工作目录>/pgstore` 下建立可写缓存,复用 `config/config.yaml` 与 `auths/` 目录。
|
2. **本地镜像:** 在 `<PGSTORE_LOCAL_PATH 或当前工作目录>/pgstore` 下建立可写缓存,复用 `config/config.yaml` 与 `auths/` 目录。
|
||||||
3. **引导:** 若数据库中无配置记录,会使用 `config.example.yaml` 初始化,并以固定标识 `config` 写入。
|
3. **引导:** 若数据库中无配置记录,会使用 `config.example.yaml` 初始化,并以固定标识 `config` 写入。
|
||||||
4. **令牌同步:** 配置与令牌的更改会写入 PostgreSQL,同时数据库中的内容也会反向同步至本地镜像,便于文件监听与管理接口继续工作。
|
4. **令牌同步:** 配置与令牌的更改会写入 PostgreSQL,同时数据库中的内容也会反向同步至本地镜像,便于文件监听与管理接口继续工作。
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ func main() {
|
|||||||
usePostgresStore bool
|
usePostgresStore bool
|
||||||
pgStoreDSN string
|
pgStoreDSN string
|
||||||
pgStoreSchema string
|
pgStoreSchema string
|
||||||
pgStoreCacheDir string
|
pgStoreLocalPath string
|
||||||
pgStoreInst *store.PostgresStore
|
pgStoreInst *store.PostgresStore
|
||||||
gitStoreLocalPath string
|
gitStoreLocalPath string
|
||||||
useGitStore bool
|
useGitStore bool
|
||||||
@@ -139,8 +139,8 @@ func main() {
|
|||||||
if value, ok := lookupEnv("PGSTORE_SCHEMA", "pgstore_schema"); ok {
|
if value, ok := lookupEnv("PGSTORE_SCHEMA", "pgstore_schema"); ok {
|
||||||
pgStoreSchema = value
|
pgStoreSchema = value
|
||||||
}
|
}
|
||||||
if value, ok := lookupEnv("PGSTORE_CACHE_DIR", "pgstore_cache_dir"); ok {
|
if value, ok := lookupEnv("PGSTORE_LOCAL_PATH", "pgstore_local_path"); ok {
|
||||||
pgStoreCacheDir = value
|
pgStoreLocalPath = value
|
||||||
}
|
}
|
||||||
useGitStore = false
|
useGitStore = false
|
||||||
}
|
}
|
||||||
@@ -169,15 +169,15 @@ func main() {
|
|||||||
// Prefer the Postgres store when configured, otherwise fallback to git or local files.
|
// Prefer the Postgres store when configured, otherwise fallback to git or local files.
|
||||||
var configFilePath string
|
var configFilePath string
|
||||||
if usePostgresStore {
|
if usePostgresStore {
|
||||||
if pgStoreCacheDir == "" {
|
if pgStoreLocalPath == "" {
|
||||||
pgStoreCacheDir = wd
|
pgStoreLocalPath = wd
|
||||||
}
|
}
|
||||||
pgStoreCacheDir = filepath.Join(pgStoreCacheDir, "pgstore")
|
pgStoreLocalPath = filepath.Join(pgStoreLocalPath, "pgstore")
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
pgStoreInst, err = store.NewPostgresStore(ctx, store.PostgresStoreConfig{
|
pgStoreInst, err = store.NewPostgresStore(ctx, store.PostgresStoreConfig{
|
||||||
DSN: pgStoreDSN,
|
DSN: pgStoreDSN,
|
||||||
Schema: pgStoreSchema,
|
Schema: pgStoreSchema,
|
||||||
SpoolDir: pgStoreCacheDir,
|
SpoolDir: pgStoreLocalPath,
|
||||||
})
|
})
|
||||||
cancel()
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ type storePersister interface {
|
|||||||
PersistAuthFiles(ctx context.Context, message string, paths ...string) error
|
PersistAuthFiles(ctx context.Context, message string, paths ...string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type authDirProvider interface {
|
||||||
|
AuthDir() string
|
||||||
|
}
|
||||||
|
|
||||||
// Watcher manages file watching for configuration and authentication files
|
// Watcher manages file watching for configuration and authentication files
|
||||||
type Watcher struct {
|
type Watcher struct {
|
||||||
configPath string
|
configPath string
|
||||||
@@ -53,6 +57,7 @@ type Watcher struct {
|
|||||||
pendingOrder []string
|
pendingOrder []string
|
||||||
dispatchCancel context.CancelFunc
|
dispatchCancel context.CancelFunc
|
||||||
storePersister storePersister
|
storePersister storePersister
|
||||||
|
mirroredAuthDir string
|
||||||
oldConfigYaml []byte
|
oldConfigYaml []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +135,12 @@ func NewWatcher(configPath, authDir string, reloadCallback func(*config.Config))
|
|||||||
w.storePersister = persister
|
w.storePersister = persister
|
||||||
log.Debug("persistence-capable token store detected; watcher will propagate persisted changes")
|
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
|
return w, nil
|
||||||
}
|
}
|
||||||
@@ -517,11 +528,15 @@ func (w *Watcher) reloadConfig() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if w.mirroredAuthDir != "" {
|
||||||
|
newConfig.AuthDir = w.mirroredAuthDir
|
||||||
|
} else {
|
||||||
if resolvedAuthDir, errResolveAuthDir := util.ResolveAuthDir(newConfig.AuthDir); errResolveAuthDir != nil {
|
if resolvedAuthDir, errResolveAuthDir := util.ResolveAuthDir(newConfig.AuthDir); errResolveAuthDir != nil {
|
||||||
log.Errorf("failed to resolve auth directory from config: %v", errResolveAuthDir)
|
log.Errorf("failed to resolve auth directory from config: %v", errResolveAuthDir)
|
||||||
} else {
|
} else {
|
||||||
newConfig.AuthDir = resolvedAuthDir
|
newConfig.AuthDir = resolvedAuthDir
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
w.clientsMutex.Lock()
|
w.clientsMutex.Lock()
|
||||||
var oldConfig *config.Config
|
var oldConfig *config.Config
|
||||||
|
|||||||
Reference in New Issue
Block a user