Manage My Damn Life 0.10.0 配置参数详解
Manage My Damn Life(MMDL)v0.10.0 全部 .env.local 配置参数分类详解:DB_* 三方言、AES_PASSWORD 主密钥行为实测、CalDAV URL 白名单与注册开关实测、会话滚动淘汰实测、SMTP/OAuth/高级参数、生产配置模板与 5 条常见问题(2026-11-02 行为级实测)
Manage My Damn Life 0.10.0 配置参数详解
目标:把 Manage My Damn Life(MMDL)v0.10.0 的全部配置参数(
.env.local)按用途分类逐项讲清:每个参数怎么改、默认值是什么、行为影响如何,并用真实环境的行为级实测(开关切换、错误密钥、白名单拒绝、会话滚动淘汰、sqlite 方言迁移)佐证。本文所有实测在 《Manage My Damn Life Docker Compose 部署指南》 所建栈上完成(2026-11-02,Ubuntu 24.04.5 + Docker 29.9.2,MMDL 0.10.0 + MySQL 8.4.13 + Radicale 3.7.6)。
一、环境信息
| 项目 | 值 |
|---|---|
| 应用 | MMDL v0.10.0(镜像 intriin/mmdl:latest,Next.js 15.5.25 / Node v20.10.0) |
| 配置文件 | 应用根目录 .env.local(compose 经 env_file 注入) |
| 配置加载器 | config/config.js(dotenv + Sequelize 连接参数) |
| 元数据库 | MySQL 8.4.13(DB_DIALECT=mysql,另实测 sqlite 方言可迁移) |
| CalDAV 后端 | Radicale 3.7.6(http://caldav:5232) |
| 访问地址 | http://localhost:8151/(NEXT_BASE_URL) |
| 演示账号 | admin / q1w2e3r4;MySQL mmdl / q1w2e3r4 |
MMDL 没有独立配置文件(无 YAML/INI),全部配置经环境变量注入;sample.env.local 是官方给出的完整参数模板(随仓库发布),部署时复制为 .env.local 再改值。
二、前置条件
- 已按部署篇完成栈搭建(MMDL 应用可访问、
/api/install/check返回success:true); - 具备编辑应用配置与重建应用容器的权限;
- 一个可用的 CalDAV 服务器及其账号(本文用 Radicale
admin/q1w2e3r4); - 本文行为验证使用 API 完成,鉴权头格式
Authorization: Basic base64(userhash:ssid)(令牌由/api/login返回,详见部署篇 4.8)。
三、参数查看与修改方式
3.1 修改方式:编辑 .env.local 后重建容器
MMDL 官方镜像把 .env.local 作为 compose env_file 注入应用进程。改完必须重建应用容器才生效:
# 修改配置后(restart 不会重读 env_file,必须 up -d 重建)
docker compose up -d app
实测回显:
Container manage-my-damn-life-0100-stack-app-1 Starting
Container manage-my-damn-life-0100-stack-app-1 Started
3.2 查看当前生效值
进入应用进程环境直接核对(本文所有实验前均用此法确认配置已生效):
docker compose exec app sh -c "env | grep -E '^(DB_|AES|DISABLE|MAX_|NEXT_BASE|DOCKER_INSTALL)' | sort"
实测回显:
AES_PASSWORD=Kx9mT2vLp7qR4wZc8bNf1jHd3sGy6uXe0aQl5tYv
DB_CHARSET=utf8mb4
DB_COLLATE=utf8mb4_general_ci
DB_DIALECT=mysql
DB_HOST=db
DB_NAME=mmdl
DB_PASS=q1w2e3r4
DB_PORT=3306
DB_USER=mmdl
DISABLE_USER_REGISTRATION=false
DOCKER_INSTALL=true
MAX_CONCURRENT_LOGINS_ALLOWED=3
MAX_OTP_VALIDITY=1800
MAX_SESSION_LENGTH=2592000
NEXT_BASE_URL=http://localhost:8151/
3.3 加载链路(源码口径)
运行时读取配置的入口是 config/config.js(供 sequelize-cli 迁移使用):
const dotenv = require('dotenv')
dotenv.config({ override: true });
const dialect = process.env.DB_DIALECT ?? "mysql"
module.exports = {
local: {
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: dialect
},
sqlite:{
dialect:"sqlite",
storage: process.env.DB_NAME
},
}
要点:dotenv.config({ override: true }) 意味着 .env.local 会覆盖同名进程环境变量;DB_DIALECT 缺省回落 mysql;sqlite 方言下 DB_NAME 被当作文件路径(storage)而非库名。Next.js 服务端 API 则直接读 process.env。
四、参数总览
按官方 sample.env.local 的四段分组 + 本文补充分类:
| 分组 | 参数 | 必需 | 默认/示例 | 详解 |
|---|---|---|---|---|
| 基础 | NEXT_BASE_URL | 是 | 无 | 5.3 |
| 数据库 | DB_HOST DB_PORT DB_USER DB_PASS DB_NAME DB_DIALECT | 是 | 无 | 5.1 |
| 数据库 | DB_CHARSET DB_COLLATE | 否 | utf8mb4 | 7.1(v0.10.0 不消费) |
| 加密 | AES_PASSWORD | 是 | 无 | 5.2 |
| CalDAV | ADDITIONAL_VALID_CALDAV_URL_LIST | 内网时是 | 注释掉 | 5.4 |
| 用户策略 | DISABLE_USER_REGISTRATION | 否 | false | 5.4 |
| 会话策略 | MAX_CONCURRENT_LOGINS_ALLOWED | 否 | 3 | 5.5 |
| 会话策略 | MAX_OTP_VALIDITY | 否 | 1800 | 5.5 |
| 会话策略 | MAX_SESSION_LENGTH | 否 | 2592000 | 5.5 |
| 会话策略 | ENFORCE_SESSION_TIMEOUT | 否 | true | 5.5 |
| 邮件 | SMTP_HOST SMTP_USERNAME SMTP_PASSWORD SMTP_FROMEMAIL SMTP_PORT SMTP_USESECURE | 否 | 占位 | 5.6 |
| OAuth | USE_NEXT_AUTH NEXTAUTH_URL NEXTAUTH_SECRET KEYCLOAK_* GOOGLE_* AUTHENTIK_* | 否 | false | 5.7 |
| 高级 | DOCKER_INSTALL | Docker 场景是 | false | 5.8 |
| 高级 | NEXT_PUBLIC_API_URL | 否 | http://localhost:3000/api | 5.3 |
| 高级 | NEXT_PUBLIC_DEBUG_MODE NEXT_API_DEBUG_MODE | 否 | 样例为 true | 5.8 |
| 高级 | NEXT_PUBLIC_SUBTASK_RECURSION_CONTROL_VAR | 否 | 100 | 5.8 |
| 高级 | NEXT_PUBLIC_TEST_MODE | 否 | false | 5.8 |
五、核心参数分类详解
5.1 数据库连接(DB_*)
| 参数 | 说明 | 本文取值 |
|---|---|---|
DB_HOST | 数据库主机;compose 场景填服务名 db | db |
DB_PORT | 端口(MySQL 3306 / Postgres 5432) | 3306 |
DB_USER / DB_PASS | 数据库账号,须与建库账号一致 | mmdl / q1w2e3r4 |
DB_NAME | 库名;sqlite 方言下是数据库文件路径 | mmdl |
DB_DIALECT | 方言:mysql / postgres / sqlite,缺省 mysql | mysql |
💡 技术拓展:
DB_DIALECT三种取值对比——
方言 存储形态 适用场景 迁移命令 mysql 独立数据库服务 官方 compose 默认,多人/长期 npx sequelize-cli db:migrate --env localpostgres 独立数据库服务 已有 PG 设施 同上( --env local)sqlite 单文件( DB_NAME即路径)单机试用/免装数据库 npx sequelize-cli db:migrate --env sqlite本文生产主线采用
mysql;sqlite方言在应用容器内实测可完成全量迁移(见下方回显),适合快速试用但无并发写保障。
sqlite 方言实测(应用容器内直接跑迁移):
docker compose exec app sh -c "cd /app && DB_DIALECT=sqlite DB_NAME=/tmp/t.sqlite npx sequelize-cli db:migrate --env sqlite 2>&1 | tail -8"
实测回显(尾部三条迁移成功):
== 20260408-columnSizeIncrease-calendars: migrated (0.027s)
== 20260408-columnSizeIncrease-users: migrating =======
== 20260408-columnSizeIncrease-users: migrated (0.009s)
== 20260527-addColumnstoCaldavAccounts: migrating =======
== 20260527-addColumnstoCaldavAccounts: migrated (0.005s)
5.2 AES_PASSWORD(CalDAV 密码主密钥)
MMDL 需要拿明文密码去登录 CalDAV 服务器,所以 CalDAV 账号密码在数据库里是可逆加密存储,AES_PASSWORD 即主密钥。
密文落库格式实测(CryptoJS OpenSSL-salt 格式,U2FsdGVkX1 为 Salted__ 的 base64 魔数):
docker compose exec db mysql -ummdl -pq1w2e3r4 mmdl -e "SELECT caldav_accounts_id, username, LEFT(password,45) AS pwd_prefix, authMethod FROM caldav_accounts;"
实测回显:
caldav_accounts_id username pwd_prefix authMethod
1 admin U2FsdGVkX19iR+SIu9MFNiwAGvhYs4id+EDLsqr4Bnw= Basic
💡 技术拓展:用户口令的三种存储策略对比——
策略 能否还原明文 典型算法 适用 单向哈希 否 bcrypt / argonaut 仅验证用途的登录口令(MMDL 的 users表即此)可逆加密(密钥固定) 是 AES-256-CBC + 主密钥 需要拿明文去第三方认证的场景(MMDL 的 CalDAV 密码即此) 令牌代持 是(由对方管理) OAuth access/refresh token USE_NEXT_AUTH/OAuth 型 CalDAV(如 Google)MMDL 因 Basic 鉴权 CalDAV 特性选择可逆 AES;主密钥泄露等价于全部 CalDAV 密码泄露,
AES_PASSWORD本身要按机密管理。
改错密钥的后果实测:把 AES_PASSWORD 改成错误值并重建应用后,刷新日历返回该账号 status: "error"(解密失败,无法向 CalDAV 认证):
# 1) 改坏密钥并重建
sed -i 's/^AES_PASSWORD=.*/AES_PASSWORD=WRONGKEY_test_0000000000000000000000000000/' .env.local
docker compose up -d app
# 2) 触发日历刷新
curl -s -X GET "http://localhost:8151/api/v2/calendars/refresh" -H "Authorization: Basic $B"
实测回显(关键字段):
status = error | message = None
# 3) 恢复原密钥并重建,再次刷新
cp .env.local.bak .env.local && rm .env.local.bak && docker compose up -d app
curl -s -X GET "http://localhost:8151/api/v2/calendars/refresh" -H "Authorization: Basic $B"
实测回显:
status = ok
⚠️ 注意:两条铁律——①
AES_PASSWORD在首次绑定 CalDAV 账号前定好,上线后不可变更(变更即全员 CalDAV 解密失败,唯一补救是用旧密钥重新加密回填或让用户重绑);② 恢复正确密钥后旧密文自动恢复可读,实测error → ok无损,说明密钥只参与加解密、不落任何派生数据。
5.3 NEXT_BASE_URL 与 API 地址
| 参数 | 说明 |
|---|---|
NEXT_BASE_URL | 应用对外根地址(含协议与端口、结尾斜杠),官方列为必需。反向代理/HTTPS 场景填公网域名 |
NEXT_PUBLIC_API_URL | 高级项。官方样例设 http://localhost:3000/api;实测 v0.10.0 前端 getAPIURL() 运行时返回相对路径 /api/,同源部署不需要设置 |
💡 说明:只有前端页面与 API 不同源(罕见拆分部署)时才需要显式设
NEXT_PUBLIC_API_URL;常规 compose/单机部署留空即可。修改NEXT_BASE_URL后重建容器,用curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:8151/验证返回HTTP 200。
5.4 CalDAV 接入控制(ADDITIONAL_VALID_CALDAV_URL_LIST / DISABLE_USER_REGISTRATION)
ADDITIONAL_VALID_CALDAV_URL_LIST(JSON 数组):MMDL 默认拒绝把 CalDAV 账号绑定到非白名单 URL(SSRF 防护)。内网 CalDAV(如 compose 服务名 http://caldav:5232)必须显式加入。
白名单拒绝行为实测——用一个不在白名单的 URL 注册:
curl -s -w '\nHTTP_CODE=%{http_code}\n' -X POST http://localhost:8151/api/v2/caldav/register \
-H "Authorization: Basic $B" -H 'Content-Type: application/json' \
-d '{"url":"http://example.com:5232","username":"admin","accountname":"Evil","password":"q1w2e3r4","auth_method":"Basic"}'
实测回显:
{"success":false,"data":{"message":"INVALID_CALDAV_DETAILS"}}
HTTP_CODE=401
而白名单内的 http://caldav:5232 注册成功(caldav_accounts_id=1,见部署篇 4.9)。写法注意:值是一行 JSON 数组,如 ["http://caldav:5232","https://cloud.example.com/remote.php/dav"];官方样例中该行是注释状态,原文为 #ADDITIONAL_VALID_CALDAV_URL_LIST = ["http://testaddress", "http://testaddress2"](dotenv 按首个 = 切分,= 两侧空格会被保留进键名/值,建议按标准写法去掉空格)。
DISABLE_USER_REGISTRATION:true 时注册接口拒绝新建用户。开关行为实测:
# 1) 置 true 并重建后尝试注册
sed -i 's/^DISABLE_USER_REGISTRATION=false/DISABLE_USER_REGISTRATION=true/' .env.local
docker compose up -d app
curl -s -w '\nHTTP_CODE=%{http_code}\n' -X POST http://localhost:8151/api/users/register \
-H 'Content-Type: application/json' -d '{"username":"seconduser","password":"q1w2e3r4","email":"second@example.com"}'
实测回显:
{"success":false,"data":{"message":"CANT_CREATE_USER"}}
HTTP_CODE=401
# 2) 恢复 false 并重建后重试
sed -i 's/^DISABLE_USER_REGISTRATION=true/DISABLE_USER_REGISTRATION=false/' .env.local
docker compose up -d app
curl -s -w '\nHTTP_CODE=%{http_code}\n' -X POST http://localhost:8151/api/users/register \
-H 'Content-Type: application/json' -d '{"username":"seconduser","password":"q1w2e3r4","email":"second@example.com"}'
实测回显:
{"success":true,"data":{"message":"USER_INSERT_OK"}}
HTTP_CODE=200
附带实测:注册接口的邮箱格式校验——email 非法时返回 {"success":"false","data":{"message":"INVALID_INPUT"}}(not-an-email 即被拒)。
💡 说明:
DISABLE_USER_REGISTRATION=true只关注册入口,已注册用户登录不受影响;官方 README 建议公开部署(有域名/公网)时开启 OAuth 或关闭注册。
5.5 会话与安全策略(MAX_* / ENFORCE_*)
| 参数 | 默认 | 行为 |
|---|---|---|
MAX_CONCURRENT_LOGINS_ALLOWED | 3 | 单用户并发会话(ssid)上限;新登录时超限滚动删除最旧会话 |
MAX_SESSION_LENGTH | 2592000(30 天) | 会话最长存活秒数 |
MAX_OTP_VALIDITY | 1800(30 分钟) | OTP 验证码有效期秒数 |
ENFORCE_SESSION_TIMEOUT | true | 是否强制执行会话超时 |
并发上限滚动淘汰实测——设 MAX_CONCURRENT_LOGINS_ALLOWED=3,将同一条登录命令连续单独执行 5 次(每次取回一个新 ssid):
# 同一条命令连跑 5 次,记录每次返回的 ssid(前 16 位)
curl -s -X POST http://localhost:8151/api/login \
-H 'Content-Type: application/json' -d '{"username":"admin","password":"q1w2e3r4"}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['data']['message']['ssid'][:16])"
实测回显(5 次执行依次得到 5 个不同令牌):
b750f1dc9e8a7954
0a517f964b1ac166
93a16387c5ccda27
a713c3cc76420f68
d26e757cd6eceedd
# 用最早的令牌 T1 调接口(B1 = base64("userhash:T1"),userhash 取任一登录响应)
curl -s -X GET "http://localhost:8151/api/v2/calendars/refresh" -H "Authorization: Basic $B1"
实测回显:
{"success":false,"data":{"message":"PLEASE_LOGIN"}}
# 查会话表行数
docker compose exec db mysql -ummdl -pq1w2e3r4 mmdl -N -e "SELECT COUNT(*) FROM ssid_table;"
实测回显:
4
解读:上限 3 却稳态 4 行——服务端逻辑是先插入新会话、再把超出部分按最旧顺序删除,观测到的稳态为 MAX+1;最早令牌被删后调用即 PLEASE_LOGIN。脚本化多端并发时要么复用令牌,要么调大该值。
5.6 SMTP 邮件通知(可选)
SMTP_HOST / SMTP_USERNAME / SMTP_PASSWORD / SMTP_FROMEMAIL / SMTP_PORT(默认 25)/ SMTP_USESECURE(默认 false):用于任务到期等邮件通知场景,官方标注"非基础功能必需"。未启用时整段留空即可(本文栈即留空,功能不受影响——安装、注册、CalDAV 读写全链路无邮件依赖)。启用时按你的邮件服务商填写(465 端口配 SMTP_USESECURE=true,587 走 STARTTLS 配 false)。
5.7 OAuth 第三方认证(USE_NEXT_AUTH 族,可选)
官方样例注释明确:第三方认证将在未来版本(约 v1.0.0)成为默认选项。参数族:
| 参数 | 说明 |
|---|---|
USE_NEXT_AUTH | false 用内置账号体系(本文主线);true 启用 NextAuth |
NEXTAUTH_URL | 须与应用域名一致 |
NEXTAUTH_SECRET | NextAuth 会话密钥(官方文档要求随机生成) |
KEYCLOAK_ISSUER_URL / KEYCLOAK_CLIENT_ID / KEYCLOAK_CLIENT_SECRET | Keycloak 接入 |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | Google 接入 |
AUTHENTIK_CLIENT_ID / AUTHENTIK_CLIENT_SECRET / AUTHENTIK_ISSUER | Authentik 接入 |
⚠️ 注意:本组参数为官方文档口径,本文未实测(实验栈保持
USE_NEXT_AUTH=false内置认证)。切换 OAuth 后内置/api/login流程不再适用,API 鉴权头机制会变化,请评估后再上生产。
5.8 高级参数
| 参数 | 默认 | 说明 |
|---|---|---|
DOCKER_INSTALL | false | Docker 镜像场景设 true,允许 /install 在容器内触发数据库迁移(本文主线设 true) |
NEXT_PUBLIC_DEBUG_MODE | 样例 true | 前端调试日志;生产设 false |
NEXT_API_DEBUG_MODE | 样例 true | 服务端 API 调试日志;生产设 false |
NEXT_PUBLIC_SUBTASK_RECURSION_CONTROL_VAR | 100 | 子任务递归查找深度上限(防递归失控);官方注释:子任务渲染不全时调大 |
NEXT_PUBLIC_TEST_MODE | false | Cypress 测试模式;生产保持 false |
六、生产配置模板
适用场景:个人/小团队(≤10 人)、2GB 内存以上 VPS、MySQL 8.4、经 Nginx 反代 HTTPS 域名访问。.env.local 全文(可直接替换域名与口令后使用):
NEXT_BASE_URL=https://mmdl.example.com/
DB_HOST=db
DB_PORT=3306
DB_USER=mmdl
DB_PASS=<强口令>
DB_NAME=mmdl
DB_DIALECT=mysql
AES_PASSWORD=<32位以上随机串,入库前定稿、永不变更>
ADDITIONAL_VALID_CALDAV_URL_LIST=["https://cloud.example.com/remote.php/dav"]
DISABLE_USER_REGISTRATION=true
MAX_CONCURRENT_LOGINS_ALLOWED=3
MAX_OTP_VALIDITY=1800
MAX_SESSION_LENGTH=2592000
ENFORCE_SESSION_TIMEOUT=true
DOCKER_INSTALL=true
NEXT_PUBLIC_DEBUG_MODE=false
NEXT_API_DEBUG_MODE=false
NEXT_PUBLIC_TEST_MODE=false
要点:公网部署三件套——DISABLE_USER_REGISTRATION=true(关自助注册)+ 反代强制 HTTPS + AES_PASSWORD/DB_PASS 换强随机值并纳入备份;ADDITIONAL_VALID_CALDAV_URL_LIST 只放行你的真实 CalDAV 域名。<强口令>/<32位以上随机串> 为注入占位,生成方式:openssl rand -base64 32。
七、常见问题
7.1 设置了 DB_CHARSET/DB_COLLATE 为什么没效果?
v0.10.0 的运行时配置加载器 config/config.js(见 3.3)不读取这两个变量——它们只出现在早期版本的建表 SQL 拼接里。现行迁移由 Sequelize 统一建表,字符集跟随 MySQL 库默认(compose 场景由 MYSQL_DATABASE 建库时的服务端默认 utf8mb4 决定)。结论:可不设置,设了也不影响行为。
7.2 改了 .env.local 为什么不生效?
docker compose restart app 只重启进程、不重读 env_file;必须 docker compose up -d app 重建容器。验证是否生效用 3.2 的 env | grep 命令核对实际注入值。
7.3 换服务器/重建栈后 CalDAV 账号全报 error?
优先核对 AES_PASSWORD 是否与原库一致(5.2):密文与主密钥绑定,密钥不匹配则 calendars/refresh 对该账号返回 status:"error"。迁移时 .env.local 与 data/mysql、data/radicale 必须整套搬迁。
7.4 绑定内网 CalDAV 返回 INVALID_CALDAV_DETAILS?
ADDITIONAL_VALID_CALDAV_URL_LIST 未包含该 URL(5.4 实测 401)。把 CalDAV 地址(含协议、端口、无结尾斜杠与白名单逐项比对)加入 JSON 数组后 up -d app 重建再试。
7.5 sqlite 方言怎么跑?
DB_DIALECT=sqlite 且 DB_NAME 填文件路径(如 /data/db/mmdl.sqlite,注意挂载持久化),迁移走 --env sqlite(5.1 实测三条 migrated 成功)。生产多人场景不建议——单文件无并发写保障。
八、参考链接
- 官方配置文档:仓库
docs/install/Configuration.md、sample.env.local(https://github.com/intri-in/manage-my-damn-life-nextjs) - NextAuth.js 配置:https://next-auth.js.org/configuration/options
- CryptoJS OpenSSL 兼容格式说明:https://github.com/brix/crypto-js
- 站内:《Manage My Damn Life 介绍与选型指南》、《Manage My Damn Life Docker Compose 部署指南》