Turtl 0.1.3 服务器配置参数详解

逐项讲清 Turtl server 0.1.3 的 config.yaml 参数、环境变量覆盖、加密盐与登录锁定、同步限额、插件与附件存储,并给出生产配置模板。

TurtlTurtlPostgreSQL配置详解2026/11/02

Turtl 0.1.3 服务器配置参数详解

目标:把 Turtl server 0.1.3 的全部配置面——config.yaml 各段参数、环境变量覆盖机制、加密盐与登录锁定、同步限额、插件与附件存储——逐项讲清默认值与影响,并给出生产配置模板。参数默认值取自官方仓库 config/config.yaml.default 与发布件内置 config/config.yaml.docker(turtl/server 源码,查询日期 2026-11-02),生效行为在 Ubuntu 26.04 部署环境实测验证(部署见本专栏 Docker Compose 部署篇)。

一、环境信息

  • 被测软件:Turtl server 0.1.3(turtl/server 源码构建,运行时基座 node:11-alpine),配置文件位于源码树 config/config.yaml(部署后为 /app/config/config.yaml,构建时由 config.yaml.docker 拷入)
  • 配置加载入口:helpers/config.js(yaml_env 库 + 环境变量覆盖)
  • 部署形态:Docker Compose(turtl-server:0.1.3 + postgres:11-alpine,宿主端口 8144),环境变量覆盖通道实测可用
  • 参考默认值文件:仓库 config/config.yaml.default(裸机默认)与 config/config.yaml.docker(发布件基线),两者差异见第四章总览表

二、前置条件

curl -s http://127.0.0.1:8144/ ; echo; curl -s http://127.0.0.1:8144/health/db
{"greeting":"Hi."}
{"healthy":true}
  • 需要核对参数语义时取源码(本文所有行为描述均以源码为准):
git clone https://github.com/turtl/server.git

三、参数查看与修改方式

Turtl 只有一个配置面:config/config.yaml(YAML),但有三条修改通道,优先级与生效条件不同(加载链见 helpers/config.js,按执行顺序):

通道方式生效条件适用
直接改 YAML编辑 config/config.yaml(裸机改文件;Docker 部署需重新构建发布件或挂载同名文件并重启)重启进程整份基线配置
环境变量覆盖TURTL_<YAML 路径大写>,如 app.api_url → TURTL_APP_API_URL、db.host → TURTL_DB_HOSTdocker compose up -d 重建服务单项覆盖,本文部署实测通道
专用环境变量TURTL_CONFIG_FILE(换配置文件名)、DATABASE_URL(postgres:// 串整体覆盖 db.*)、PORT(覆盖 server.port)、TURTL_CONFIG_OVERRIDE(JSON 串按顶层键整段替换)重启进程特殊场景

加载链源码事实(helpers/config.js):

var config_file = 'config.yaml';
if(process.env['TURTL_CONFIG_FILE']) {
	config_file = process.env['TURTL_CONFIG_FILE'];
}
var config = yaml_env.load('TURTL', __dirname+'/../config/'+config_file);
// 之后依次: DATABASE_URL 拆填 db.* → PORT 覆盖 server.port → TURTL_CONFIG_OVERRIDE 逐顶层键替换

⚠️ 注意:环境变量前缀是 TURTL_(yaml_env.load('TURTL', ...) 的第一个参数)。官方 README 正文把前缀误写为 TURTLE_,但其示例 TURTL_APP_API_URL 才是正确形态——照 README 正文写变量名会静默不生效。

配置没有「查询当前值」的 API 端点,验证手段按参数类型选择:启动日志(监听端口、控制器加载清单)、行为回显(健康检查、认证、同步限额)、访问日志(loglevel 影响 morgan 输出级别)。

四、参数总览

config.yaml.default 与发布件基线 config.yaml.docker 的全部键对照(默认值列自 default 文件原文):

段.参数default 值docker 基线值说明
server.host空(全接口)空监听地址,注释:默认监听所有 IP,可用此参数指定
server.port81818181监听端口;PORT 环境变量可覆盖
db.connstr'postgres://slappy:floppy@127.0.0.1:5432/turtl'未设整串连接;DATABASE_URL 环境变量优先级更高
db.host/port/database/user/password未设(走 connstr)'db'/5432/'turtl'/'turtl'/''五元组形式,TURTL_DB_* 覆盖
db.pool2424连接池大小
loglevel'debug''info'winston 日志级别
app.enable_bookmarker_proxyfalsefalse须写魔法字符串才加载 bookmarking 控制器
app.api_url'http://api.yourdomain.com:8181''http://127.0.0.1:8181'服务器对外 API 地址(无尾斜杠)
app.www_url'https://yourdomain.com''https://yourdomain.com'官网地址(确认/删除跳转目标)
app.login.max_attemps55失败登录锁定阈值,-1 关闭(键名拼写就是 attemps)
app.login.lock_duration6060锁定时长(秒)
app.emails.admin/info/invitesadmin@turtlapp.com 等同 default系统邮件收件人/发件人
app.secure_hash_salt官方示例长句同 default(构建期被环境变量顶替)全局加密盐,必填必填再必填(5.4)
app.allow_unconfirmed_invitesfalsetrue未确认账号可否发邀请
sync.max_bulk_sync_records3232单次批量同步上限,超出静默截断
plugins.plugin_location'/var/www/turtl/server/plugins''/plugins'插件根目录
plugins.email.enabled/endpoint/defaultsfalse/'smtps://user:password@smtp.gmail.com/?pool=true'/{}false/空/{}邮件插件(确认/邀请信)
plugins.analytics.enabled未设false统计插件
plugins.premium.enabled未设false付费功能插件
uploads.local'/var/www/turtl/server/public/uploads''/uploads'本地附件目录;设 false 则走 S3
uploads.local_proxytruetrue本地文件经服务端代理下载(免 CORS)
uploads.url'http://api.turtl.dev/uploads''http://127.0.0.1:8181/uploads'不代理时附件的公开 URL 前缀
s3.token/secret/bucket/endpoint/pathstyle占位值/'https://s3.amazonaws.com'/false同 defaultS3 附件存储备选

两处 docker 基线与 default 的关键差异:db.host: 'db'(compose 服务名)与 allow_unconfirmed_invites: true(方便测试);生产务必按第六章改回。

五、核心参数分类详解

5.1 server 监听与数据库连接

  • server.host 留空时启动日志为 info: Listening for turtls on port 8181...(实测原文);设置具体 IP 后日志变为 Listening for turtls on IP <地址>, port 8181...(源码 server.js 第 66–72 行分支)。
  • db.connstr 与五元组二选一;DATABASE_URL 环境变量存在且以 postgres: 开头时,helpers/config.js 解析 URL 并拆填 db.host/port/database/user/password,优先级高于 YAML 与环境变量五元组。
  • db.pool 默认 24:连接池上限,单机小团队保持默认即可。
  • 生效验证(本文部署以 TURTL_DB_* 五元组覆盖发布件基线 db.host: 'db'):
curl -s http://127.0.0.1:8144/health/db
{"healthy":true}

5.2 loglevel 与访问日志

loglevel 取 winston 级别(debug/info),default 文件是 debug、发布件基线是 info。除应用日志外,server.js 挂 morgan 中间件输出逐请求访问日志,格式串(源码原文):

morgan(':remote-addr ":method :url" :status :res[content-length]', ...)

实测访问日志行(部署环境回显):

info: ::ffff:10.89.61.1 "GET /" 200 18
info: ::ffff:10.89.61.1 "GET /health/db" 200 16

排障同步协议(如 5.8 的静默截断)时临时调 TURTL_LOGLEVEL: debug 重建服务。

5.3 app.api_url 与 app.www_url

  • app.api_url:服务器对外 API 地址(注释明示 no trailing slash)。确认/删除邮件中的链接以此为目标,反向代理 + TLS 部署时必须填公网 https 地址,否则用户点邮件链接会打到内网地址。本文部署实测值 http://127.0.0.1:8144(经 TURTL_APP_API_URL 覆盖)。
  • app.www_url:官网/前端地址。controllers/users.js 的确认与删除流程用 tres.redirect(res, config.app.www_url+'/users/confirm/success', ...) 做 307 跳转——即确认链接点完后浏览器被导向 <www_url>/users/confirm/success,填错会跳到不存在的前端。

5.4 app.secure_hash_salt(全局加密盐,最高风险参数)

源码用途有两处(models/user.js):

  1. 登录凭据落库:pbkdf2(auth, secure_hash_salt, 2 次迭代, base64)——auth 是客户端生成的不透明 blob,低迭代次数是源码注释明示的有意设计;
  2. 服务端私有数据加密:pbkdf2(privatedata, secure_hash_salt, 100000 次迭代, sha256)。

硬约束与风险:

  • 入口脚本强制校验:未设 TURTL_APP_SECURE_HASH_SALT 时打印 TURTL_APP_SECURE_HASH_SALT is unset. 并 exit 1,进程根本无法启动(本文部署实测通道)。
  • 上线后更换盐值 = 所有既有账号 auth 校验立即失效(库里存的是旧盐的 pbkdf2 结果),且私有数据加密随之失效。生成一次(如 openssl rand -hex 32)、备份、永不轻改。
  • default 文件里那句「Plaque is a figment...」长文本是官方占位示例,生产必须替换。

5.5 app.login 登录锁定

check_auth(models/user.js 第 89–110 行)流程:查 users 表 → active 检查 → 失败锁定检查 → secure_compare(user.auth, pbkdf2(auth, salt, 2))。

参数默认行为
max_attemps5失败次数达到阈值即锁;设 -1 关闭锁定(注释原文)。键名拼写为 attemps(上游如此),写成 attempts 不生效
lock_duration60锁定时长(秒),窗口内再错继续拒绝 user locked

失败计数落 users.login_failed_count/login_failed_last 字段(注册回显里即可见这两个字段初值 0/null)。

5.6 app.allow_unconfirmed_invites 与 app.emails

  • allow_unconfirmed_invites:控制「账号未确认时能否发送共享邀请」。default false(生产稳妥),docker 基线 true(方便测试)——生产部署建议改回 false。
  • app.emails.admin/info/invites:系统邮件的收件人与发件人署名,配合 email 插件使用。

5.7 app.enable_bookmarker_proxy

书签导入代理开关。server.js 加载控制器列表时,bookmarking 一项要求配置值精确等于字符串 I UNDERSTAND THIS VIOLATES THE PRIVACY OF MY USERS 才加载(default 文件注释原文:ALWAYS false in production. Always.)。实测启动日志的控制器清单没有 bookmarking:

info: Loading controller: users
info: Loading controller: sync
info: Loading controller: spaces
info: Loading controller: files
info: Loading controller: feedback
info: Loading controller: errlog
info: Loading controller: cla
info: Loading controller: health

该功能会让服务器代客户端抓取 URL(历史书签导入用),与零知识定位冲突,官方用「手写隐私声明」的方式阻止误开。

5.8 sync.max_bulk_sync_records

单次 POST /sync 允许的最大记录数,默认 32。源码行为(models/sync.js 第 405–410 行):

var max_sync_records = (config.sync || {}).max_bulk_sync_records;
if(max_sync_records) {
	sync_records = sync_records.slice(0, max_sync_records);
}

⚠️ 注意:超出部分是 slice 静默截断——不报错、不进 failures,客户端若一次推 40 条会只落 32 条且响应看不出异常。大批量推送必须客户端自行分片(每片 ≤32),这是自写客户端最容易踩的限额。

5.9 plugins 插件段

plugins.plugin_location 指向目录(部署目录内 /plugins,构建时 install-plugins.sh 装入),目录下每个含 main.js 的子目录即一个插件(server.js 第 51–64 行遍历加载)。配置键与目录同名,每插件至少含 enabled 布尔:

  • email:确认信/邀请信投递(nodemailer endpoint,smtps://user:password@host/?pool=true 形式)。enabled false 时确认邮件静默跳过——注册成功但账号停在 confirmed:false(实测回显见部署篇 4.5),分享邀请受限。
  • analytics/premium:统计与付费钩子,自托管默认关。

5.10 uploads 与 s3(附件存储)

  • uploads.local 非 false 时附件写本地目录(部署目录内 /uploads——本地磁盘路径,重装即丢,生产要么挂出来要么改 false 走 S3)。
  • uploads.local_proxy: true 时下载走服务端代理路由 GET /notes/:note_id/local-attachment-proxy(免配 web 服务器 CORS,注释原话:高并发下略影响性能)。
  • local_proxy: false 时 uploads.url 必须是附件对客户端可达的公开 URL 前缀。
  • s3.{token,secret,bucket,endpoint,pathstyle}:本地存储设 false 后的备选;pathstyle: true 用于 MinIO 等非 AWS 兼容端点。

六、生产配置模板

适用场景:16GB 内存 / 50GB SSD 单机、10 人以内团队笔记、经反向代理以 https 对外:

server:
  host:
  port: 8181

db:
  host: 'postgres-db'
  port: 5432
  database: 'turtl'
  user: 'turtl'
  password: 'q1w2e3r4'
  pool: 24

loglevel: 'info'

app:
  enable_bookmarker_proxy: false
  api_url: 'https://notes.example.com'      # 反向代理后的公网 API 地址,无尾斜杠
  www_url: 'https://www.example.com'        # 你的前端(www 仓库自部署)地址
  login:
    max_attemps: 5
    lock_duration: 60
  emails:
    admin: 'admin@example.com'
    info: 'Turtl <info@example.com>'
    invites: 'invites@example.com'
  secure_hash_salt: '<openssl rand -hex 32 生成,备份后永不更改>'
  allow_unconfirmed_invites: false

sync:
  max_bulk_sync_records: 32

plugins:
  plugin_location: '/plugins'
  email:
    enabled: true
    endpoint: 'smtps://user:password@smtp.example.com/?pool=true'
    defaults: {}

uploads:
  local: '/uploads'      # 务必在 compose 里挂载 ./uploads:/uploads
  local_proxy: true
  url: 'https://notes.example.com/uploads'

s3:
  token: ''
  secret: ''
  bucket: ''
  endpoint: 'https://s3.amazonaws.com'
  pathstyle: false

要点:盐值一次生成永久使用;api_url/www_url 填公网域名否则邮件链接与 307 跳转会坏;email 插件配好 SMTP 才能让成员完成确认、开放共享;/uploads 必须持久化挂载或改走 S3。Docker 形态下以上任意项也可不碰文件,直接用 TURTL_<路径大写> 环境变量覆盖(第三章)。

七、常见问题

Q1:环境变量设了 TURTLE_APP_API_URL 没生效? 前缀写错。README 正文的 TURTLE_ 是笔误,实际前缀由 yaml_env.load('TURTL', ...) 决定,是 TURTL_(如 TURTL_APP_API_URL)。改完 docker compose up -d 重建服务生效。

Q2:改了 compose 里的环境变量,服务行为没变? 配置只在进程启动时读取一次,docker compose restart 不会重读 compose 文件里的 environment——必须 docker compose up -d(检测到配置差异时重建服务)。

Q3:换了 secure_hash_salt 后所有客户端都报 bad login: (bad auth)? 预期行为(5.4):库里 auth 是旧盐的 pbkdf2 结果,新盐校验必然失败且不可逆。恢复旧盐是唯一出路,这就是「盐值备份、永不轻改」的原因。

Q4:一次推 40 条 sync 记录,只落了 32 条且无报错? max_bulk_sync_records 的 slice 静默截断(5.8)。客户端按 ≤32 分片推送,或调大该参数(不建议,防滥用初衷见 default 文件注释)。

Q5:注册后一直 confirmed:false,邀请发不出去? email 插件 enabled: false 时确认邮件被静默跳过(5.9)。配好 SMTP 后调 POST /users/confirmation/resend 重发确认,或临时开 allow_unconfirmed_invites(仅测试环境)。

Q6:想启用书签导入代理? 把 app.enable_bookmarker_proxy 精确设为 I UNDERSTAND THIS VIOLATES THE PRIVACY OF MY USERS(5.7),启动日志出现 Loading controller: bookmarking 即生效;生产环境请三思——它让服务器代抓 URL,违背零知识定位。

八、参考链接

Turtl 0.1.3 服务器配置参数详解 - Quzuo