Skip to content

Commit 27cc8bb

Browse files
committed
feat(admin): improve setup and runtime settings
Add first-run setup helpers, runtime status reporting, admin auth flow updates, docs quick-start options, and configurable background media controls. Raise MP4 dynamic wallpaper uploads to 40MB while keeping image uploads capped at 20MB.
1 parent 058fa1c commit 27cc8bb

31 files changed

Lines changed: 3317 additions & 103 deletions

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ Notes:
166166
- Before switching deployment modes, replace `.env` with the matching example file.
167167
- The SQLite lightweight mode runs a single `codex2api` container and stores data at `/data/codex2api.db`.
168168
- **SQLite compose files bind to `127.0.0.1` by default for security.** To expose the SQLite service on all interfaces, set `BIND_HOST=0.0.0.0` in `.env` or override the port binding in the compose file. The standard compose files bind to `0.0.0.0` by default.
169-
- The image studio library is stored under `/data/images`; Docker configurations persist `/data`.
169+
- The image studio library is stored under `/data/images`; uploaded admin backgrounds are stored under `/data/backgrounds`; Docker configurations persist `/data`.
170170
- `docker compose down` does not delete named volumes by default. Data is removed only by commands such as `docker compose down -v`, `docker volume rm`, or `docker volume prune`.
171171

172172
---
@@ -238,6 +238,7 @@ Vite proxies `/api` and `/health` to the backend. During development, open `http
238238
| Variable | Description |
239239
| --- | --- |
240240
| `CODEX_PORT` | HTTP port, default `8080` |
241+
| `CODEX_MAX_REQUEST_BODY_SIZE_MB` | HTTP request body limit in MB, default `48` |
241242
| `ADMIN_SECRET` | Admin dashboard secret. When set, `/admin` prompts for authentication |
242243
| `DATABASE_DRIVER` | Database driver: `postgres` or `sqlite` |
243244
| `DATABASE_PATH` | SQLite database file path, used when `DATABASE_DRIVER=sqlite` |

README.zh-CN.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ docker compose -f docker-compose.sqlite.local.yml logs -f codex2api
211211
- SQLite 本地构建版容器名:`codex2api-sqlite-local`
212212
- SQLite 轻量版只启动 `codex2api` 单容器,数据保存在 `/data/codex2api.db`
213213
- **SQLite compose 文件默认绑定 `127.0.0.1`,仅本机可访问。** 如需暴露给外部,请在 `.env` 中设置 `BIND_HOST=0.0.0.0` 或修改 compose 文件中的端口绑定。标准版 compose 文件默认绑定 `0.0.0.0`(所有网络接口)。
214-
- 生图工作台图库默认保存在 `/data/images`,标准版和 SQLite 版 Docker 配置都会持久化 `/data`
214+
- 生图工作台图库默认保存在 `/data/images`上传的后台背景默认保存在 `/data/backgrounds`标准版和 SQLite 版 Docker 配置都会持久化 `/data`
215215
- `docker compose down` 默认不会删除命名卷;只有 `docker compose down -v``docker volume rm``docker volume prune` 才会删除持久化数据
216216
- 不同部署模式的数据卷彼此隔离;切换 compose 文件后看到空数据,通常是切到了另一组卷,而不是原卷被自动删除
217217

@@ -289,6 +289,7 @@ Vite 会自动代理 `/api` 和 `/health` 到后端,开发时访问 `http://lo
289289
| 变量 | 说明 |
290290
| --- | --- |
291291
| `CODEX_PORT` | HTTP 端口,默认 `8080` |
292+
| `CODEX_MAX_REQUEST_BODY_SIZE_MB` | HTTP 请求体上限,单位 MB,默认 `48` |
292293
| `ADMIN_SECRET` | 管理后台登录密钥;设置后首次访问 `/admin` 会弹出密码输入框 |
293294
| `DATABASE_DRIVER` | 数据库驱动,支持 `postgres` / `sqlite` |
294295
| `DATABASE_PATH` | SQLite 数据文件路径,`DATABASE_DRIVER=sqlite` 时生效 |

admin/bootstrap.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"unicode/utf8"
1313

1414
"github.com/codex2api/database"
15+
"github.com/codex2api/internal/imagestore"
1516
"github.com/codex2api/proxy"
1617
"github.com/codex2api/security"
1718
"github.com/gin-gonic/gin"
@@ -78,6 +79,119 @@ func bootstrapAllowClientIP(clientIP string) bool {
7879
return false
7980
}
8081

82+
func firstForwardedHeaderValue(value string) string {
83+
value = strings.TrimSpace(value)
84+
if value == "" {
85+
return ""
86+
}
87+
if idx := strings.Index(value, ","); idx >= 0 {
88+
value = value[:idx]
89+
}
90+
return strings.TrimSpace(value)
91+
}
92+
93+
func bootstrapPublicBaseURL(r *http.Request) string {
94+
if r == nil {
95+
return ""
96+
}
97+
proto := firstForwardedHeaderValue(r.Header.Get("X-Forwarded-Proto"))
98+
if proto == "" {
99+
if r.TLS != nil {
100+
proto = "https"
101+
} else {
102+
proto = "http"
103+
}
104+
}
105+
host := firstForwardedHeaderValue(r.Header.Get("X-Forwarded-Host"))
106+
if host == "" {
107+
host = strings.TrimSpace(r.Host)
108+
}
109+
if host == "" {
110+
return ""
111+
}
112+
return strings.TrimRight(proto+"://"+host, "/")
113+
}
114+
115+
func bootstrapDatabaseLocation(driver string) string {
116+
if strings.EqualFold(driver, "sqlite") {
117+
return strings.TrimSpace(os.Getenv("DATABASE_PATH"))
118+
}
119+
host := strings.TrimSpace(os.Getenv("DATABASE_HOST"))
120+
name := strings.TrimSpace(os.Getenv("DATABASE_NAME"))
121+
port := strings.TrimSpace(os.Getenv("DATABASE_PORT"))
122+
if host == "" || name == "" {
123+
return ""
124+
}
125+
if port != "" {
126+
host += ":" + port
127+
}
128+
return host + "/" + name
129+
}
130+
131+
func (h *Handler) bootstrapSetupHints(r *http.Request) gin.H {
132+
serviceURL := bootstrapPublicBaseURL(r)
133+
adminURL := ""
134+
apiBaseURL := ""
135+
if serviceURL != "" {
136+
adminURL = strings.TrimRight(serviceURL, "/") + "/admin/"
137+
apiBaseURL = strings.TrimRight(serviceURL, "/") + "/v1"
138+
}
139+
databaseDriver := h.databaseDriver
140+
databaseLabel := h.databaseLabel
141+
if databaseDriver == "" && h.db != nil {
142+
databaseDriver = h.db.Driver()
143+
}
144+
if databaseLabel == "" && h.db != nil {
145+
databaseLabel = h.db.Label()
146+
}
147+
cacheDriver := h.cacheDriver
148+
cacheLabel := h.cacheLabel
149+
if cacheDriver == "" && h.cache != nil {
150+
cacheDriver = h.cache.Driver()
151+
}
152+
if cacheLabel == "" && h.cache != nil {
153+
cacheLabel = h.cache.Label()
154+
}
155+
156+
usageLogMode := database.UsageLogModeFull
157+
usageLogBatchSize := 200
158+
usageLogFlushIntervalSeconds := 5
159+
if h.db != nil {
160+
usageLogMode = h.db.GetUsageLogMode()
161+
usageLogBatchSize = h.db.GetUsageLogBatchSize()
162+
usageLogFlushIntervalSeconds = h.db.GetUsageLogFlushIntervalSeconds()
163+
}
164+
165+
imageBackend := imagestore.CurrentConfig().Backend
166+
if imageBackend == "" {
167+
imageBackend = imagestore.BackendLocal
168+
}
169+
170+
return gin.H{
171+
"service_url": serviceURL,
172+
"admin_url": adminURL,
173+
"api_base_url": apiBaseURL,
174+
"database": gin.H{
175+
"driver": databaseDriver,
176+
"label": databaseLabel,
177+
"location": bootstrapDatabaseLocation(databaseDriver),
178+
},
179+
"cache": gin.H{
180+
"driver": cacheDriver,
181+
"label": cacheLabel,
182+
},
183+
"data": gin.H{
184+
"image_local_dir": imageAssetDir(),
185+
"image_storage_backend": imageBackend,
186+
},
187+
"usage": gin.H{
188+
"log_mode": usageLogMode,
189+
"batch_size": usageLogBatchSize,
190+
"flush_interval_seconds": usageLogFlushIntervalSeconds,
191+
},
192+
}
193+
}
194+
81195
// GetBootstrapStatus 返回当前是否需要执行初始化(GET /api/admin/bootstrap-status)。
82196
//
83197
// 该端点不要求鉴权,前端 AuthGate 在拿到登录界面前会先轮询此端点:
@@ -116,9 +230,18 @@ func (h *Handler) GetBootstrapStatus(c *gin.Context) {
116230
c.JSON(http.StatusOK, gin.H{
117231
"needs_bootstrap": true,
118232
"source": "empty",
233+
"setup": h.bootstrapSetupHints(c.Request),
119234
})
120235
}
121236

237+
// GetSetupHints 返回登录后的部署检查信息。
238+
//
239+
// 与公开的 bootstrap-status 不同,该接口注册在 /api/admin 下,需要管理密钥,
240+
// 因此可以安全返回数据库位置、图片目录等部署诊断信息。
241+
func (h *Handler) GetSetupHints(c *gin.Context) {
242+
c.JSON(http.StatusOK, h.bootstrapSetupHints(c.Request))
243+
}
244+
122245
// PostBootstrap 接收用户在浏览器中输入的初始管理密钥并写入数据库。
123246
//
124247
// 安全约束:

0 commit comments

Comments
 (0)