运行时设置
把支付、存储、邮件签名和 Turnstile 保存在数据库中,由管理员在 /app/admin/settings 维护。
支付网关凭证、S3 存储、头像公开读域名、邮件签名密钥和 Turnstile 存放在 app_setting 表里,由管理员在 /app/admin/settings 维护。
邮件发送、Better Auth 和 OAuth 仍使用环境变量。邮件签名留空时回退到 BETTER_AUTH_SECRET。头像读域名留空时使用 src/config/storage.ts 里的 AVATARS_FILE_PROXY_URL_FALLBACK。
首位管理员
注册仍走环境变量里的邮件(OTP)。用 pnpm muse:user:create 创建用户,再用 pnpm muse:user:role 提权,然后打开 /app/admin/settings。
哪些留在 env,哪些进后台
| 表面 | 来源 | 原因 |
|---|---|---|
| 站点 URL、数据库、Better Auth、OAuth | 环境变量 | 在任何管理员会话之前就必须可用 |
邮件(MAIL_PROVIDER、Resend / Cloudflare) | 环境变量 | 设置表为空时注册 OTP 仍需工作 |
MUSE_SETTINGS_SECRET | 环境变量 | 设置加密的包装密钥,绝不入库 |
| 支付网关、webhook secret、默认网关 | 后台设置 | 仅服务端密钥,落库加密 |
| 结账产品 ID | 后台设置(billing.productMap) | 结账真源 |
| S3 access key、secret、endpoint、头像 bucket、公开读域名 | 后台设置(存储) | 凭证加密落库。读域名通过 /api/auth/get-session 发给已登录客户端 |
| 邮件签名密钥 | 后台设置(邮件) | 可选。留空使用 BETTER_AUTH_SECRET。更换后已发出的退订链接失效 |
| Turnstile 站点密钥和密钥 | 后台设置(验证码) | 两项都保存后,忘记密码页才会显示验证码 |
首次部署顺序
配置 BETTER_AUTH_SECRET、MUSE_SETTINGS_SECRET、数据库和邮件环境变量。MUSE_SETTINGS_SECRET 至少 32 个字符。
执行迁移(pnpm drizzle:migrate),确保存在 app_setting 与 app_setting_revision。
创建并提权管理员(pnpm muse:user:create,然后 pnpm muse:user:role)。
打开 /app/admin/settings,保存网关凭证、结账产品 ID 和 S3 设置。上线前先点 测试连接。存储页还有头像公开读域名。邮件和验证码是各自独立的标签。
计费页顶部是 默认网关 卡片。每个支付商(Creem、Stripe、Dodo、Waffo)默认 收起。展开后在同一面板里填写该网关凭证,以及对应的结账产品 ID(Pro 月付 / 年付 / Lifetime)。标题右侧会显示「默认」「已配置 / 未配置」、已映射产品 ID 数量,以及未保存时的「未保存」。
可选:pnpm muse:settings:import 把本地残留的网关/存储凭证一次性写入数据库。之后运行时不再读取这些 env。结账产品 ID 在后台设置里填写,不走环境变量。
加密
密钥字段以无前缀的 base64url 密文存储。每一行用 HKDF-SHA256(info 绑定 scope:key)派生 AES-256-GCM 密钥,附加数据同样绑定该字段身份。把密文挪到别的字段会解密失败。算法版本记在 cipher_version;非密钥字段为 0(明文)。
能力降级
没有网关时,登录后的定价 CTA 不可点,发起结账会返回可读错误。没有 S3 时,头像/文件接口返回 STORAGE_ENV_MISSING(503),存储页显示空态。公开首页保持静态,不查询这些设置。
缓存
设置按进程缓存,并以 app_setting_revision 为键。其他实例最长 30 秒后刷新。