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 LifeManage My Damn Life自托管CalDAVNext.jsMySQL2026/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否utf8mb47.1(v0.10.0 不消费)
加密AES_PASSWORD是无5.2
CalDAVADDITIONAL_VALID_CALDAV_URL_LIST内网时是注释掉5.4
用户策略DISABLE_USER_REGISTRATION否false5.4
会话策略MAX_CONCURRENT_LOGINS_ALLOWED否35.5
会话策略MAX_OTP_VALIDITY否18005.5
会话策略MAX_SESSION_LENGTH否25920005.5
会话策略ENFORCE_SESSION_TIMEOUT否true5.5
邮件SMTP_HOST SMTP_USERNAME SMTP_PASSWORD SMTP_FROMEMAIL SMTP_PORT SMTP_USESECURE否占位5.6
OAuthUSE_NEXT_AUTH NEXTAUTH_URL NEXTAUTH_SECRET KEYCLOAK_* GOOGLE_* AUTHENTIK_*否false5.7
高级DOCKER_INSTALLDocker 场景是false5.8
高级NEXT_PUBLIC_API_URL否http://localhost:3000/api5.3
高级NEXT_PUBLIC_DEBUG_MODE NEXT_API_DEBUG_MODE否样例为 true5.8
高级NEXT_PUBLIC_SUBTASK_RECURSION_CONTROL_VAR否1005.8
高级NEXT_PUBLIC_TEST_MODE否false5.8

五、核心参数分类详解

5.1 数据库连接(DB_*)

参数说明本文取值
DB_HOST数据库主机;compose 场景填服务名 dbdb
DB_PORT端口(MySQL 3306 / Postgres 5432)3306
DB_USER / DB_PASS数据库账号,须与建库账号一致mmdl / q1w2e3r4
DB_NAME库名;sqlite 方言下是数据库文件路径mmdl
DB_DIALECT方言:mysql / postgres / sqlite,缺省 mysqlmysql

💡 技术拓展:DB_DIALECT 三种取值对比——

方言存储形态适用场景迁移命令
mysql独立数据库服务官方 compose 默认,多人/长期npx sequelize-cli db:migrate --env local
postgres独立数据库服务已有 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 tokenUSE_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_ALLOWED3单用户并发会话(ssid)上限;新登录时超限滚动删除最旧会话
MAX_SESSION_LENGTH2592000(30 天)会话最长存活秒数
MAX_OTP_VALIDITY1800(30 分钟)OTP 验证码有效期秒数
ENFORCE_SESSION_TIMEOUTtrue是否强制执行会话超时

并发上限滚动淘汰实测——设 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_AUTHfalse 用内置账号体系(本文主线);true 启用 NextAuth
NEXTAUTH_URL须与应用域名一致
NEXTAUTH_SECRETNextAuth 会话密钥(官方文档要求随机生成)
KEYCLOAK_ISSUER_URL / KEYCLOAK_CLIENT_ID / KEYCLOAK_CLIENT_SECRETKeycloak 接入
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETGoogle 接入
AUTHENTIK_CLIENT_ID / AUTHENTIK_CLIENT_SECRET / AUTHENTIK_ISSUERAuthentik 接入

⚠️ 注意:本组参数为官方文档口径,本文未实测(实验栈保持 USE_NEXT_AUTH=false 内置认证)。切换 OAuth 后内置 /api/login 流程不再适用,API 鉴权头机制会变化,请评估后再上生产。

5.8 高级参数

参数默认说明
DOCKER_INSTALLfalseDocker 镜像场景设 true,允许 /install 在容器内触发数据库迁移(本文主线设 true)
NEXT_PUBLIC_DEBUG_MODE样例 true前端调试日志;生产设 false
NEXT_API_DEBUG_MODE样例 true服务端 API 调试日志;生产设 false
NEXT_PUBLIC_SUBTASK_RECURSION_CONTROL_VAR100子任务递归查找深度上限(防递归失控);官方注释:子任务渲染不全时调大
NEXT_PUBLIC_TEST_MODEfalseCypress 测试模式;生产保持 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 成功)。生产多人场景不建议——单文件无并发写保障。

八、参考链接

Manage My Damn Life 0.10.0 配置参数详解 - Quzuo