Trilium v0.106.0 常用 CLI 命令与工具详解
Trilium 服务端无独立 CLI,官方自动化通道是 ETAPI。本文以 curl 逐端点实测 v0.106.0:认证取 token、笔记与附件 CRUD、搜索、克隆、导出、备份、指标、注销全部回显照抄,并覆盖内建 MCP server、健康检查与日志三个运维入口。
Trilium v0.106.0 常用 CLI 命令与工具详解
目标:系统梳理 Trilium v0.106.0 服务端的命令行工具面。Trilium 服务器没有独立 CLI 二进制,官方提供的自动化通道是 ETAPI(REST API)——本文以 curl 逐端点实测:认证取 token、笔记 CRUD、正文与修订、搜索(含标签过滤)、附件、属性、克隆、导出、备份、指标、注销等,全部命令与响应均实测照抄;另覆盖健康检查、日志与内建 MCP server 三个运维入口。部署与配置见 《Trilium v0.106.0 Docker Compose 部署指南》、《Trilium v0.106.0 配置参数详解》。
一、环境信息
- 操作系统:Ubuntu 22.04;Trilium v0.106.0 自托管实例(
app=0.106.0 db=240 sync=39 clipper=1.0,启动日志实测)。 - 服务地址:
http://localhost:8139(宿主端口 8139 → 服务 8080,数据目录/opt/trilium/data)。 - 客户端工具:curl(系统自带);认证凭据:登录密码
q1w2e3r4(演示账户 admin,密码统一为测试口令)。 - 实测时间:2026-10-22。所有 JSON 响应均为当日实测回显,noteId/attachmentId 等 ID 为当次实测实例真实值,读者操作时会得到不同的随机 ID。
二、工具概览
Trilium 服务器的运维/自动化入口共四类,分工如下:
| 工具 | 形态 | 用途 | 本文覆盖 |
|---|---|---|---|
| ETAPI | REST API(curl 即可调用) | 笔记/附件/属性/分支的增删改查、搜索、备份、指标 | 四~八节逐端点实测 |
| 内建 MCP server | MCP 协议端点 /mcp | 供 MCP 客户端(AI 助手等)调用,需 ETAPI token | 9.1 节 |
| 健康检查 | HTTP 端点 /api/health-check | 存活探测(监控/负载均衡用) | 9.2 节 |
| 运行日志 | 数据目录 log/ 与服务日志 | 排障与审计 | 9.3 节 |
💡 技术拓展:给 Trilium 做自动化的接口选择不止 ETAPI 一种——
通道 特点 适用 ETAPI(REST) 官方稳定 API,OpenAPI 规范齐备,token 鉴权 脚本/CI/外部系统集成 脚本 API(前端/后端脚本) 笔记内嵌脚本直接调 api.*,能力更细但受安全开关约束单实例内自动化 MCP server(v0.106 新增) 标准 MCP 协议封装 ETAPI AI 助手/IDE 集成 本文主线用 ETAPI:跨语言、无侵入、官方 OpenAPI 规范随服务附带(安装目录
assets/etapi.openapi.yaml,共 33 个路径定义)。
三、前置条件
- 服务已部署并可访问(验证):
curl -s http://localhost:8139/api/health-check
{"status":"ok"}
- 已设置登录密码(首次初始化时设置;
POST /set-password表单字段password1/password2)。 - 取得 ETAPI token(见 4.1 节)。
jq等解析工具可选(本文用 grep/python 解析示例均可独立复现)。
四、ETAPI 基础约定
4.1 认证:用密码换取 token
POST /etapi/auth/login,JSON 体传登录密码(该端点在 OpenAPI 规范中显式声明 security: [],即无需预置 token):
curl -s -X POST http://localhost:8139/etapi/auth/login \
-H "Content-Type: application/json" \
-d '{"password":"q1w2e3r4"}' -w "\nHTTP %{http_code}\n"
{"authToken":"918BZvmbnJNc_hB6LUvILPWcL+rtbbVsnUoBZMSZA2iVxNqX+SaFKxRk="}
HTTP 201
成功返回 HTTP 201 与 authToken。后续所有 ETAPI 请求把该值放进 Authorization 头(不加 Bearer 前缀):
TOKEN='918BZvmbnJNc_hB6LUvILPWcL+rtbbVsnUoBZMSZA2iVxNqX+SaFKxRk='
curl -s http://localhost:8139/etapi/app-info -H "Authorization: $TOKEN"
{"appVersion":"0.106.0","dbVersion":240,"syncVersion":39,"buildDate":"2026-09-25T18:09:29Z","buildRevision":"c0fdceba3b59af8a4473c6944b4f176121bb9788","clipperProtocolVersion":"1.0","utcDateTime":"2026-10-21T18:22:24.918Z","nodeVersion":"v24.21.0","dataDirectory":"/home/node/trilium-data"}
token 错误或缺失时的统一错误结构(实测):
curl -s http://localhost:8139/etapi/app-info -H "Authorization: wrong-token-123"
{"status":401,"code":"NOT_AUTHENTICATED","message":"Not authenticated"}
⚠️ 短时间内大量失败认证会触发客户端 IP 黑名单(HTTP 429),OpenAPI 规范对
/auth/login明确列有该响应:"Client IP has been blacklisted because too many requests…try again later"。脚本重试请加退避。
4.2 端点总清单(OpenAPI 规范实查,33 个路径)
| 域 | 端点 |
|---|---|
| 笔记 | POST /create-note、GET /notes(搜索)、GET/PATCH/DELETE /notes/{noteId}、GET/PUT /notes/{noteId}/content、GET /notes/{noteId}/export、POST /notes/{noteId}/import、GET /notes/{noteId}/revision、GET /notes/{noteId}/revisions、GET /notes/{noteId}/attachments、POST /notes/{noteId}/undelete、GET /notes/history |
| 修订 | GET/PATCH/DELETE /revisions/{revisionId}、GET/PUT /revisions/{revisionId}/content |
| 分支 | POST /branches、GET/PATCH/DELETE /branches/{branchId} |
| 附件 | POST /attachments、GET/PATCH/DELETE /attachments/{attachmentId}、GET/PUT /attachments/{attachmentId}/content |
| 属性 | POST /attributes、GET/PATCH/DELETE /attributes/{attributeId} |
| 树维护 | POST /refresh-note-ordering/{parentNoteId} |
| 日历 | GET /inbox/{date}、GET /calendar/days/{date}、GET /calendar/week-first-day/{date}、GET /calendar/weeks/{week}、GET /calendar/months/{month}、GET /calendar/years/{year} |
| 认证 | POST /auth/login、POST /auth/logout |
| 实例 | GET /app-info、PUT /backup/{backupName}、GET /metrics |
五、笔记管理:CRUD 与搜索实测
5.1 创建笔记
TOKEN='918BZvmbnJNc_hB6LUvILPWcL+rtbbVsnUoBZMSZA2iVxNqX+SaFKxRk='
curl -s -X POST http://localhost:8139/etapi/create-note \
-H "Authorization: $TOKEN" -H "Content-Type: application/json" \
-d '{"parentNoteId":"root","title":"ETAPI 测试笔记","type":"text","content":"<p>通过 ETAPI 创建的第一条笔记</p>"}'
{"note":{"noteId":"GvnycwzglJsU","isProtected":false,"title":"ETAPI 测试笔记","type":"text","mime":"text/html","blobId":"f5asrwwfRJ0gDdtTxRIs","dateCreated":"2026-10-22 02:26:08.571+0800","dateModified":"2026-10-22 02:26:08.572+0800","utcDateCreated":"2026-10-21 18:26:08.571Z","utcDateModified":"2026-10-21 18:26:08.572Z","parentNoteIds":["root"],"childNoteIds":[],"parentBranchIds":["root_GvnycwzglJsU"],"childBranchIds":[],"attributes":[]},"branch":{"branchId":"root_GvnycwzglJsU","noteId":"GvnycwzglJsU","parentNoteId":"root","prefix":null,"notePosition":40,"isExpanded":false,"utcDateModified":"2026-10-21 18:26:08.573Z"}}
一次返回 note(笔记实体)与 branch(挂载关系)两个对象。parentNoteId:"root" 即挂到根;type 支持 text/code/file/image/search/relation map/canvas 等官方笔记类型。
5.2 读取与改标题
NID=GvnycwzglJsU
curl -s http://localhost:8139/etapi/notes/$NID -H "Authorization: $TOKEN"
{"noteId":"GvnycwzglJsU","isProtected":false,"title":"ETAPI 测试笔记",…}
curl -s -X PATCH http://localhost:8139/etapi/notes/$NID \
-H "Authorization: $TOKEN" -H "Content-Type: application/json" \
-d '{"title":"ETAPI 测试笔记(已改名)"}' -w "\nHTTP %{http_code}\n"
{"noteId":"GvnycwzglJsU","isProtected":false,"title":"ETAPI 测试笔记(已改名)","type":"text","mime":"text/html","blobId":"f5asrwwfRJ0gDdtTxRIs","dateCreated":"2026-10-22 02:26:08.571+0800","dateModified":"2026-10-22 02:26:14.524+0800",…}
HTTP 200
5.3 修改正文(⚠️ Content-Type 只有两种合法取值)
curl -s -X PUT http://localhost:8139/etapi/notes/$NID/content \
-H "Authorization: $TOKEN" -H "Content-Type: text/html" \
-d '<p>更新后的正文内容 v2</p>' -w "\nHTTP %{http_code}\n"
{"status":500,"code":"GENERIC","message":"Cannot set null content to noteId 'GvnycwzglJsU'"}
HTTP 500
这是排障实录:PUT /notes/{id}/content 只接受 text/plain(文本内容,如 text 笔记的 HTML 源码)或 application/octet-stream(二进制,如图片/文件笔记),传 text/html 会被当作空内容拒绝。改用 text/plain:
curl -s -X PUT http://localhost:8139/etapi/notes/$NID/content \
-H "Authorization: $TOKEN" -H "Content-Type: text/plain" \
-d '<p>更新后的正文内容 v2</p>' -w "HTTP %{http_code}\n"
HTTP 204
读回验证:
curl -s http://localhost:8139/etapi/notes/$NID/content -H "Authorization: $TOKEN"
<p>更新后的正文内容 v2</p>
5.4 修订(note revisions)
正文再次变化后,Trilium 自动为上一版生成修订(source:"auto"):
curl -s -X PUT http://localhost:8139/etapi/notes/$NID/content \
-H "Authorization: $TOKEN" -H "Content-Type: text/plain" \
-d '<p>更新后的正文内容 v3</p>' -w "PUT -> HTTP %{http_code}\n"
sleep 3
curl -s http://localhost:8139/etapi/notes/$NID/revisions -H "Authorization: $TOKEN"
PUT -> HTTP 204
[{"revisionId":"CnwN9Od9VH9L","noteId":"GvnycwzglJsU","type":"text","mime":"text/html","isProtected":false,"title":"ETAPI 测试笔记(已改名)","description":"","source":"auto","blobId":"Oxl3XzDobJ9s5nsS1sms","dateLastEdited":"2026-10-22 02:27:10.784+0800","dateCreated":"2026-10-22 02:40:09.733+0800","utcDateLastEdited":"2026-10-21 18:27:10.784Z","utcDateCreated":"2026-10-21 18:40:09.733Z","utcDateModified":"2026-10-21 18:40:09.738Z","contentLength":18}]
⚠️ 实测注意:首次 PUT 后立刻查修订列表返回
[]——修订是在下一次内容变化时基于上一份 blob 生成的,不是每次保存立即产生。
5.5 搜索(含标签过滤)
关键词全文搜索:
curl -s "http://localhost:8139/etapi/notes?search=ETAPI" -H "Authorization: $TOKEN"
{"results":[{"noteId":"GvnycwzglJsU","isProtected":false,"title":"ETAPI 测试笔记(已改名)",…}]}
按标签/属性过滤(#status=testing 需 URL 编码为 %23status%3Dtesting):
curl -s "http://localhost:8139/etapi/notes?search=%23status%3Dtesting" -H "Authorization: $TOKEN"
{"results":[{"noteId":"GvnycwzglJsU","isProtected":false,"title":"ETAPI 测试笔记(已改名)",…}]}
常用查询参数(OpenAPI 规范实查):search(查询串,支持 Trilium 完整搜索语法如精确匹配 "..."、标签 #label)、fastSearch(true 时不搜正文,更快)、includeArchivedNotes(默认忽略归档笔记)、subtreeNoteId(限定子树)、orderBy(排序属性名)。
5.6 删除与恢复
curl -s -X DELETE http://localhost:8139/etapi/notes/$NID -H "Authorization: $TOKEN" -w "HTTP %{http_code}\n"
HTTP 204
删除后搜索验证(结果为空)并恢复:
curl -s "http://localhost:8139/etapi/notes?search=ETAPI&includeArchivedNotes=true" -H "Authorization: $TOKEN"
{"results":[]}
curl -s -X POST http://localhost:8139/etapi/notes/$NID/undelete -H "Authorization: $TOKEN" -w "HTTP %{http_code}\n"
{"success":true}
HTTP 200
六、附件与属性管理
6.1 创建附件(⚠️ 属主字段是 ownerId)
curl -s -X POST http://localhost:8139/etapi/attachments \
-H "Authorization: $TOKEN" -H "Content-Type: application/json" \
-d '{"ownerNoteId":"GvnycwzglJsU","title":"readme.txt","role":"file","mime":"text/plain","content":"hello attachment"}'
{"status":400,"code":"PROPERTY_NOT_ALLOWED","message":"Property 'ownerNoteId' is not allowed for this method."}
排障实录:创建附件的属主字段名是 ownerId(可指向 noteId 或 revisionId),不是 ownerNoteId。修正重试:
curl -s -X POST http://localhost:8139/etapi/attachments \
-H "Authorization: $TOKEN" -H "Content-Type: application/json" \
-d '{"ownerId":"GvnycwzglJsU","title":"readme.txt","role":"file","mime":"text/plain","content":"hello attachment"}'
{"attachmentId":"naHA86Fa4gnA","ownerId":"GvnycwzglJsU","role":"file","mime":"text/plain","title":"readme.txt","position":10,"blobId":"I1VQBi5ozydDCbXXuxcO","dateModified":"2026-10-22 02:26:47.862+0800","utcDateModified":"2026-10-21 18:26:47.862Z"}
读附件内容与列表:
AID=naHA86Fa4gnA
curl -s http://localhost:8139/etapi/attachments/$AID/content -H "Authorization: $TOKEN"
hello attachment
curl -s http://localhost:8139/etapi/notes/GvnycwzglJsU/attachments -H "Authorization: $TOKEN"
[{"attachmentId":"naHA86Fa4gnA","ownerId":"GvnycwzglJsU","role":"file","mime":"text/plain","title":"readme.txt","position":10,"blobId":"I1VQBi5ozydDCbXXuxcO","dateModified":"2026-10-22 02:26:47.862+0800","utcDateModified":"2026-10-21 18:26:47.862Z","utcDateScheduledForErasureSince":null,"contentLength":16}]
6.2 打标签(label 属性)
curl -s -X POST http://localhost:8139/etapi/attributes \
-H "Authorization: $TOKEN" -H "Content-Type: application/json" \
-d '{"noteId":"GvnycwzglJsU","type":"label","name":"status","value":"testing"}'
{"attributeId":"lkADxni1GSFA","noteId":"GvnycwzglJsU","type":"label","name":"status","value":"testing","position":10,"isInheritable":false,"utcDateModified":"2026-10-21 18:26:26.927Z"}
type 取 label(标签)或 relation(关系);打完标签即可被 5.5 节的属性搜索命中。
七、结构管理与导出
7.1 分支克隆
curl -s -X POST http://localhost:8139/etapi/branches \
-H "Authorization: $TOKEN" -H "Content-Type: application/json" \
-d '{"noteId":"GvnycwzglJsU","parentNoteId":"root","prefix":"克隆"}'
{"branchId":"root_GvnycwzglJsU","noteId":"GvnycwzglJsU","parentNoteId":"root","prefix":"克隆","notePosition":40,"isExpanded":false,"utcDateModified":"2026-10-21 18:26:53.564Z"}
实测语义:目标父节点下已存在该笔记的挂载时,返回的就是既有分支对象并更新 prefix;克隆到新父节点才生成新 branchId。这与"单笔记多位置挂载"的克隆模型一致。
7.2 导出笔记(返回 ZIP 包)
NID=GvnycwzglJsU
curl -s "http://localhost:8139/etapi/notes/$NID/export?format=markdown" -H "Authorization: $TOKEN" -o export-demo.zip
file export-demo.zip && unzip -o -q export-demo.zip -d export-demo && ls export-demo/ && cat export-demo/*.md
export-demo.zip: Zip archive data, at least v2.0 to extract, compression method=deflate
!!!meta.json
ETAPI 测试笔记(已改名).md
# ETAPI 测试笔记(已改名)
更新后的正文内容 v2
format=markdown 返回 ZIP 归档:内含笔记的 .md 文件与 !!!meta.json 元数据清单(导入时依赖)。format 另支持 html 与 opml(大纲类)。
八、实例运维端点
8.1 在线备份(⚠️ PUT 不是 POST)
curl -s -X POST http://localhost:8139/etapi/backup/now -H "Authorization: $TOKEN" -w "\nHTTP %{http_code}\n"
{"message":"Router not found for request POST /etapi/backup/now"}
HTTP 404
排障实录:备份端点的方法是 PUT:
curl -s -X PUT http://localhost:8139/etapi/backup/now -H "Authorization: $TOKEN" -w "HTTP %{http_code}\n"
HTTP 204
产物为数据目录下 backup/backup-now.db(实测 ls /opt/trilium/data/backup/ → backup-now.db,约 3.1 MB)。backupName 即文件名组成:backup-<名称>.db。
8.2 实例指标(Prometheus 文本格式)
curl -s http://localhost:8139/etapi/metrics -H "Authorization: $TOKEN" | head -14
# HELP trilium_info Trilium instance information
# TYPE trilium_info gauge
trilium_info{version="0.106.0",db_version="240",node_version="v24.21.0",sync_version="39",build_date="2026-09-25T18:09:29Z",build_revision="c0fdceba3b59af8a4473c6944b4f176121bb9788"} 1
# HELP trilium_notes_total Total number of notes including deleted
# TYPE trilium_notes_total gauge
trilium_notes_total 578
# HELP trilium_notes_deleted Number of deleted notes
# TYPE trilium_notes_deleted gauge
trilium_notes_deleted 0
# HELP trilium_notes_active Number of active notes
# TYPE trilium_notes_active gauge
trilium_notes_active 578
指标可直接被抓取型监控系统采集(官方文档配套 Grafana Dashboard)。
8.3 笔记历史(按日期)
curl -s "http://localhost:8139/etapi/notes/history?date=2026-10-22" -H "Authorization: $TOKEN" | head -c 300
[{"noteId":"GvnycwzglJsU","current_isDeleted":0,"current_deleteId":null,"current_title":"ETAPI 测试笔记(已改名)","current_isProtected":0,"title":"ETAPI 测试笔记(已改名)","utcDate":"2026-10-21 18:26:08.571Z","date":"2026-10-22 02:26:08.571+0800"},{…}]
8.4 注销 token
curl -s -X POST http://localhost:8139/etapi/auth/logout -H "Authorization: <要注销的token>" -w "HTTP %{http_code}\n"
HTTP 204
注销后再用该 token 访问即被拒(实测回显 {"status":401,"code":"NOT_AUTHENTICATED","message":"Not authenticated"})。轮换凭据时先登录新 token、再注销旧 token 即可实现无缝切换。
九、其他运维入口
9.1 内建 MCP server(v0.106 新增)
启动日志实测输出:MCP server registered at /mcp (ETAPI token required)——v0.106.0 起服务端内建 MCP(Model Context Protocol)端点,路径 /mcp,鉴权与 ETAPI 相同(Authorization 头携带 ETAPI token)。把 <服务器地址>/mcp 注册进 MCP 客户端并配置 token 后,AI 助手即可直接读写笔记库。
9.2 健康检查端点
curl -s -w "\nHTTP %{http_code}\n" http://localhost:8139/api/health-check
{"status":"ok"}
HTTP 200
镜像内置健康检查即指向该端点(数据目录 healthcheck-url 文件实测内容 http://127.0.0.1:8080/api/health-check)。
9.3 运行日志
日志位于数据目录 log/ 子目录(/opt/trilium/data/log/),启动横幅含版本三元组与端口信息:
📦 Versions: app=0.106.0 db=240 sync=39 clipper=1.0
🔧 Build: 2026-09-25 18:09:29 (c0fdceba3b)
📂 Data dir: /home/node/trilium-data
💾 DB size: 3.2 MiB
Listening on port 8080
每次 API 调用都会记入日志(方法、路径、状态码、耗时),如 02:24:02.816 POST /api/setup/new-document rejected with 401 App already initialized.,排障时直接按时间检索。
十、注意事项
- Authorization 头不加 Bearer 前缀,直接放 token 值(与多数 API 习惯不同)。
- PUT content 的 Content-Type 白名单:仅
text/plain与application/octet-stream(5.3 节实测排障)。 - 附件属主字段是
ownerId(6.1 节实测排障);PATCH 附件仅role/mime/title/position可改。 - 备份是 PUT(8.1 节实测排障)。
- ID 都是当次实例的随机值:本文所有 noteId/branchId/attachmentId 仅作回展示例,读者操作时以自己实例返回为准。
- 限流与黑名单:认证失败过多会 429(4.1 节);脚本重试务必退避。
- ETAPI token 等同账号密码,泄露后立即
POST /auth/logout注销;定期轮换。
十一、常见问题
Q1:ETAPI 返回 401 "Not authenticated"?
依次检查:token 是否过期/被注销;Authorization 头是否带了 Bearer 前缀(不该带);token 值是否被 shell 转义破坏(token 含 +/=,赋值变量时用单引号)。
Q2:PUT content 报 "Cannot set null content"?
Content-Type 用错(5.3 节)。文本笔记一律 text/plain(内容放 HTML 源码),二进制一律 application/octet-stream。
Q3:创建附件报 PROPERTY_NOT_ALLOWED "ownerNoteId"?
字段名写错了,正确是 ownerId(6.1 节)。
Q4:备份 404 "Router not found"?
方法用错,备份是 PUT /etapi/backup/<名称>(8.1 节),不是 POST。
Q5:搜索不到刚删的笔记?
搜索默认忽略已删除笔记;传 includeArchivedNotes=true 也查不到已删除内容——Trilium 的删除是软删 + 恢复窗口(POST /notes/{id}/undelete 可恢复,5.6 节实测),彻底清理靠服务端的删除清除周期。
Q6:想看全部可调用的端点?
随服务附带的 OpenAPI 规范文件 assets/etapi.openapi.yaml(33 个路径)即权威清单,配合 curl http://localhost:8139/etapi/app-info 确认实例版本后对照使用。
十二、参考链接
- 官方 ETAPI 文档:https://docs.triliumnotes.org/user-guide/advanced-usage/etapi
- OpenAPI 规范(v0.106.0):https://github.com/TriliumNext/Trilium/blob/v0.106.0/apps/server/src/assets/etapi.openapi.yaml
- 搜索语法文档:https://docs.triliumnotes.org/user-guide/advanced-usage/search
- Metrics 与 Grafana:https://docs.triliumnotes.org/user-guide/advanced-usage/metrics
- 本专栏相关篇目:《Trilium 介绍与选型指南》、《Trilium v0.106.0 Docker Compose 部署指南》、《Trilium v0.106.0 配置参数详解》