系统设置迁入后台:站点地址 / OAuth 凭据 / R2 存储 / 管理员账号,保存即生效无需重启

- 生效规则统一为「后台填了用后台的,没填回落环境变量」,老部署不改 env 照常跑
- config.Resolver:短缓存解析有效配置,存储后端按配置签名热重建;后台保存主动失效
- 秘密项(client secret / bot token / R2 密钥 / 管理员密码)接口永不回显明文,
  只报「是否已配置、来自哪里」;留空保存 = 保持现值
- 管理员密码 bcrypt 入库,DB 哈希优先、显式设置的 env 密码作解锁后路;
  后台改过密码后 admin/admin 开发模式立即失效
- 设置页新增「登录与存储」标签,基础信息加站点地址;秘密项带来源提示
- 监听地址 / 数据库 / 目录 / ONE_SECRET / Passkey 仍留环境变量(启动期依赖)
This commit is contained in:
Sakurasan committed 2026-10-01 12:14:10 +08:00
1 parent bf3ce934dd
commit e68400b389
23 files changed
+957 -126

No files matched your search

+46
View File
@@ -226,6 +226,26 @@ func (s *Store) seedSettings() error {
return nil
}
// GetSettingsMap 返回 settings 表的原始 KV。后台设置页用它判断每个
// 配置项的来源(DB 有值 / 回落环境变量)——这个信息在解析后的
// model.Settings 里已经丢了(空串既可能是「没配」也可能是「回落 env」)。
func (s *Store) GetSettingsMap() (map[string]string, error) {
rows, err := s.db.Query(s.db.Q(`SELECT key, value FROM settings`))
if err != nil {
return nil, err
}
defer rows.Close()
m := map[string]string{}
for rows.Next() {
var k, v string
if err := rows.Scan(&k, &v); err != nil {
return nil, err
}
m[k] = v
}
return m, rows.Err()
}
// ---------- settings ----------
func (s *Store) GetSettings() (model.Settings, error) {
@@ -272,6 +292,21 @@ func settingsFromMap(m map[string]string) model.Settings {
st.CustomJS = m["custom_js"]
st.SocialLinks = decodeSocialLinks(m["social_links"])
st.AuthorAvatarKey = m["owner_avatar_key"]
// 从环境变量迁进后台的系统设置(DB 值优先,空值由 config.Overlay 回落 env)
st.SiteURL = strings.TrimSpace(m["site_url"])
st.GitHubClientID = strings.TrimSpace(m["github_client_id"])
st.GoogleClientID = strings.TrimSpace(m["google_client_id"])
st.TelegramBot = strings.TrimSpace(m["telegram_bot"])
st.AdminUsername = strings.TrimSpace(m["admin_username"])
st.UploadsPublicBase = strings.TrimRight(strings.TrimSpace(m["uploads_public_base"]), "/")
st.S3Endpoint = strings.TrimSpace(m["s3_endpoint"])
st.R2Bucket = strings.TrimSpace(m["r2_bucket"])
st.R2AccessKey = m["r2_access_key"]
st.R2SecretKey = m["r2_secret_key"]
st.GitHubClientSecret = m["github_client_secret"]
st.GoogleClientSecret = m["google_client_secret"]
st.TelegramToken = m["telegram_bot_token"]
st.AdminPasswordHash = m["admin_password_hash"]
// 开关类:'1' / 'true' 都算开,其余(含空)算关
st.CommentsEnabled = m["comments_enabled"] == "1" || strings.EqualFold(m["comments_enabled"], "true")
st.CommentsReview = m["comments_review"] == "1" || strings.EqualFold(m["comments_review"], "true")
@@ -462,6 +497,17 @@ func (s *Store) UpdateSettings(st model.Settings) error {
// 开关统一存 '1' / '0'。
"comments_enabled": b2s(st.CommentsEnabled),
"comments_review": b2s(st.CommentsReview),
// 从环境变量迁进后台的系统设置(非秘密项)。秘密项不在这里写——
// 它们经过 model.Settings 的 json:"-" 反序列化永远是空串,
// 全量替换会把配置清掉;改秘密走 admin settings PUT 的 secrets 字段。
"site_url": strings.TrimSpace(st.SiteURL),
"github_client_id": strings.TrimSpace(st.GitHubClientID),
"google_client_id": strings.TrimSpace(st.GoogleClientID),
"telegram_bot": strings.TrimSpace(st.TelegramBot),
"admin_username": strings.TrimSpace(st.AdminUsername),
"uploads_public_base": strings.TrimRight(strings.TrimSpace(st.UploadsPublicBase), "/"),
"s3_endpoint": strings.TrimSpace(st.S3Endpoint),
"r2_bucket": strings.TrimSpace(st.R2Bucket),
}
for k, v := range sets {
if s.db.Dialect == db.Postgres {