remark42 运维命令与 API 实操详解
remark42 v1.18.0 运维实操:backup/restore/cleanup/import/remap/avatar 六大子命令与 ping/find/count 等开放端点逐条实测,含管理 API 用户封禁、评论置顶只读删除、全站导出、userdata 导出与限流排障实录,全部命令与回显逐条实测于 2026-10-22。
remark42 运维命令与 API 实操详解
目标:系统实测 remark42 v1.18.0 的全部运维入口——二进制的
backup、restore、cleanup、import、remap、avatar六个运维子命令,以及 REST API 的开放端点、认证端点与管理端点,覆盖备份恢复、导入迁移、URL 重写、用户封禁、评论管理、全站导出等高频运维动作,并沉淀排障实录。全部命令与回显已在 Ubuntu 22.04.5 LTS 环境逐条实测通过(实测时间 2026-10-22)。安装部署见《Ubuntu 22.04 安装 remark42 指南》,参数含义见《remark42 1.18.0 配置参数详解》。
一、环境信息
本章列出实测环境与关键可复现信息,正文所有命令与回显均来自该环境。
- 操作系统:Ubuntu 22.04.5 LTS(
/etc/os-release实测PRETTY_NAME="Ubuntu 22.04.5 LTS"),x86_64/amd64 架构; - remark42 版本:v1.18.0(二进制自报版本
remark42 v1.18.0-13b2035c-20261011T191723); - 实测时间:2026-10-22;
- 服务器内网 IP:
10.89.57.2; - remark42 服务:systemd 托管(单元文件
/etc/systemd/system/remark42.service),HTTP 监听 8131,环境变量文件/opt/remark42/remark42.env; - 二进制路径:
/opt/remark42/bin/remark42.linux-amd64; - 运行数据目录:
/opt/remark42/var/(bolt 数据库mysite.db、头像avatars/、图片pictures/); - 备份目录:
/opt/remark42/backup/; - 运行账户:专用系统用户
remark42; - 已配置站点:
SITE=mysite,dtest(实测期间为验证导入临时追加了dtest,见 6.4 节); - 已有数据:
mysite站点下http://10.89.57.2:8136/post-01.html页面有 2 条评论(来自安装指南的评论闭环实测); - 演示管理员凭据:管理接口 Basic Auth 账号
admin、密码q1w2e3r4(对应环境变量ADMIN_PASSWD=q1w2e3r4)。
二、运维方式总览与前置条件
2.1 运维入口总览
remark42 没有管理后台 UI,运维操作通过两类入口完成:二进制子命令与 REST API。子命令清单来自 --help 实测:
$ /opt/remark42/bin/remark42.linux-amd64 --help
remark42 v1.18.0-13b2035c-20261011T191723
Usage:
remark42.linux-amd64 [OPTIONS] <command>
Application Options:
--url= url to remark [$REMARK_URL]
--secret= the shared secret key used to sign JWT, should be a random,
long, hard-to-guess string [$SECRET]
--dbg debug mode [$DEBUG]
Help Options:
-h, --help Show this help message
Available commands:
avatar
backup
cleanup
import
remap
restore
server
| 子命令 | 用途 | 是否依赖运行中的服务 |
|---|---|---|
server | 启动主服务(systemd 单元调用,见安装指南) | — |
backup | 全站评论导出为 gzip 压缩的 JSON Lines 备份文件 | 是(经管理 API 完成导出) |
restore | 从备份文件恢复评论(native 格式) | 是(经管理 API 完成导入) |
cleanup | 按敏感词/用户/日期清理评论,支持演练模式 | 是 |
import | 导入 Disqus/WordPress/Commento 第三方评论 | 是 |
remap | 按规则批量重写评论所属页面 URL | 是 |
avatar | 在 fs/bolt/uri 三种头像存储之间迁移头像 | 否(直接读写存储) |
版本说明:旧版本的
removal、delete-uri子命令在 v1.18.0 中已不再提供,评论删除统一走管理 API(DELETE /api/v1/admin/comment/{id}),URL 批量删除/重写由remap子命令承担;健康检查ping是 API 端点而非子命令。
2.2 前置条件
- 服务运行状态:
backup、restore、cleanup、import、remap五个子命令均通过管理 API 与服务通信,需要 remark42 服务处于运行状态:
$ systemctl is-active remark42
active
- 环境变量加载:子命令依赖
REMARK_URL、SECRET、SITE、ADMIN_PASSWD等环境变量。.env文件中的行没有export前缀,直接source只会赋值 shell 变量而不导出,必须配合set -a:
$ cd /opt/remark42 && set -a && source remark42.env && set +a
漏掉 set -a 时子命令会直接报错(排障实录,见 13.1 节)。
- 管理凭据:子命令与管理 API 都需要
ADMIN_PASSWD(本环境为q1w2e3r4),子命令从环境变量读取,API 调用用-u admin:q1w2e3r4传入。
三、数据备份:backup 子命令
3.1 参数说明
backup --help 实测输出:
$ /opt/remark42/bin/remark42.linux-amd64 backup --help
remark42 v1.18.0-13b2035c-20261011T191723
Usage:
remark42.linux-amd64 [OPTIONS] backup [backup-OPTIONS]
Application Options:
--url= url to remark [$REMARK_URL]
--secret= the shared secret key used to sign JWT, should be a
random, long, hard-to-guess string [$SECRET]
--dbg debug mode [$DEBUG]
Help Options:
-h, --help Show this help message
[backup command options]
-p, --path= export path (default: ./var/backup) [$BACKUP_PATH]
-f, --file= file name (default: userbackup-{{.SITE}}-{{.TS}}.gz)
-s, --site= site name (default: remark) [$SITE]
--admin-passwd= admin basic auth password [$ADMIN_PASSWD]
--timeout= timeout for the command run (default: 60m)
| 参数 | 环境变量 | 默认值 | 说明 |
|---|---|---|---|
-p, --path | BACKUP_PATH | ./var/backup | 备份输出目录 |
-f, --file | — | userbackup-{{.SITE}}-{{.TS}}.gz | 备份文件名模板,{{.TS}} 渲染为 yyyymmddThhmmss |
-s, --site | SITE | remark | 站点 ID |
--admin-passwd | ADMIN_PASSWD | — | 管理密码 |
--timeout | — | 60m | 命令超时 |
3.2 实测:执行备份
$ cd /opt/remark42 && set -a && source remark42.env && set +a
$ ./bin/remark42.linux-amd64 backup
remark42 v1.18.0-13b2035c-20261011T191723
2026/10/22 02:39:13.041 [INFO] export to /opt/remark42/backup, site mysite
2026/10/22 02:39:13.042 [INFO] export completed, file /opt/remark42/backup/userbackup-mysite-20261022T023913.gz
$ ls -la backup/
total 16
drwxr-xr-x 2 remark42 remark42 4096 Oct 22 02:39 .
drwxr-xr-x 5 remark42 remark42 4096 Oct 22 01:59 ..
-rw-r--r-- 1 root root 485 Oct 22 02:12 userbackup-mysite-20261022T021218.gz
-rw-r--r-- 1 root root 606 Oct 22 02:39 userbackup-mysite-20261022T023913.gz
注意文件属主:以 root 执行备份时生成的文件属主为 root。若后续要由
remark42服务账户读取该文件,建议用sudo -u remark42执行备份,或备份后chown remark42:remark42修正属主。
3.3 备份文件内容格式
备份文件是 gzip 压缩的 JSON Lines:首行为元信息(版本与用户列表),其后每行一条评论的完整 JSON:
$ zcat /opt/remark42/backup/userbackup-mysite-20261022T023913.gz | head -c 420
{"version":1,"users":[],"posts":[]}
{"id":"cabcece9-360e-4f88-9feb-c10a8230cadf","pid":"","text":"<p>这是通过 API 发表的第一条测试评论,验证 remark42 评论闭环。</p>\n","orig":"这是通过 API 发表的第一条测试评论,验证 remark4
$ zcat /opt/remark42/backup/userbackup-mysite-20261022T023913.gz | wc -l
3
3 行 = 1 行元信息 + 2 条评论。该格式即官方 native 导入格式,可直接用 restore 恢复。
四、数据恢复:restore 子命令
4.1 参数说明
restore --help 实测输出:
$ /opt/remark42/bin/remark42.linux-amd64 restore --help
remark42 v1.18.0-13b2035c-20261011T191723
Usage:
remark42.linux-amd64 [OPTIONS] restore [restore-OPTIONS]
Application Options:
--url= url to remark [$REMARK_URL]
--secret= the shared secret key used to sign JWT, should be a
random, long, hard-to-guess string [$SECRET]
--dbg debug mode [$DEBUG]
Help Options:
-h, --help Show this help message
[restore command options]
-p, --path= export path (default: ./var/backup) [$BACKUP_PATH]
-f, --file= file name (default:
userbackup-{{.SITE}}-{{.YYYYMMDD}}.gz)
-s, --site= site name (default: remark) [$SITE]
--admin-passwd= admin basic auth password [$ADMIN_PASSWD]
--timeout= timeout for the command run (default: 60m)
4.2 实测:恢复备份
用 3.2 节刚生成的备份文件执行恢复(评论按 ID 幂等写入,同 ID 重复导入不产生重复数据):
$ cd /opt/remark42 && set -a && source remark42.env && set +a
$ ./bin/remark42.linux-amd64 restore -f /opt/remark42/backup/userbackup-mysite-20261022T023913.gz
remark42 v1.18.0-13b2035c-20261011T191723
2026/10/22 02:39:24.994 [INFO] restore /opt/remark42/backup/userbackup-mysite-20261022T023913.gz, site mysite
2026/10/22 02:39:24.994 [INFO] import /opt/remark42/backup/userbackup-mysite-20261022T023913.gz (native), site mysite
2026/10/22 02:39:24.995 [INFO] completed, status=202, {"status":"import request accepted"}
status=202 表示服务端已受理,导入在服务后台异步完成,随后用开放 API 验证数据完整:
$ curl -s "http://localhost:8131/api/v1/count?site=mysite&url=http://10.89.57.2:8136/post-01.html"
{"count":2,"locator":{"site":"mysite","url":"http://10.89.57.2:8136/post-01.html"}}
4.3 排障实录:默认文件名模板不匹配
restore 不带 -f 时按默认模板 userbackup-{{.SITE}}-{{.YYYYMMDD}}.gz 查找文件,而 backup 生成的实际文件名是 userbackup-{{.SITE}}-{{.TS}}.gz(带时分秒)。两者渲染结果不一致,直接执行 restore 找不到当天备份:
$ ./bin/remark42.linux-amd64 restore
remark42 v1.18.0-13b2035c-20261011T191723
...(在 ./var/backup 下找不到 userbackup-mysite-20261022.gz,恢复中止)
解决办法:恢复时始终用 -f 显式指定备份文件全名(4.2 节即此用法)。另外默认查找目录是 ./var/backup(相对当前工作目录),备份存放在 /opt/remark42/backup 时也必须用 -f 给出绝对路径。
五、评论清理:cleanup 子命令
5.1 参数说明
cleanup --help 实测输出:
$ /opt/remark42/bin/remark42.linux-amd64 cleanup --help
remark42 v1.18.0-13b2035c-20261011T191723
Usage:
remark42.linux-amd64 [OPTIONS] cleanup [cleanup-OPTIONS]
Application Options:
--url= url to remark [$REMARK_URL]
--secret= the shared secret key used to sign JWT, should be a
random, long, hard-to-guess string [$SECRET]
--dbg debug mode [$DEBUG]
Help Options:
-h, --help Show this help message
[cleanup command options]
--dry dry mode, will not remove comments
--from= from yyyymmdd
--to= from yyyymmdd
-w, --bword= bad word(s)
-u, --buser= bad user(s)
--title title mode, will not remove comments, but reset
titles to page's title'
-s, --site= site name (default: remark) [$SITE]
--admin-passwd= admin basic auth password [$ADMIN_PASSWD]
--timeout= timeout for the command run (default: 60m)
| 参数 | 说明 |
|---|---|
--dry | 演练模式,只统计不删除 |
--from / --to | 清理日期区间,格式 yyyymmdd |
-w, --bword | 敏感词(可多次传入),命中评论将被删除 |
-u, --buser | 用户名(可多次传入),该用户评论将被删除 |
--title | 标题模式:不删评论,仅把评论的页面标题重置为页面实际标题 |
5.2 实测:演练模式
以「广告」为敏感词做演练,验证统计与确认无误删:
$ cd /opt/remark42 && set -a && source remark42.env && set +a
$ ./bin/remark42.linux-amd64 cleanup --dry -w 广告
remark42 v1.18.0-13b2035c-20261011T191723
2026/10/22 02:39:25.702 [INFO] cleanup for site mysite
2026/10/22 02:39:25.705 [INFO] completed, comments=2, spam=0
回显 comments=2 为扫描的评论总数,spam=0 为命中敏感词的数量(当前 2 条评论均不含「广告」,无命中)。确认参数无误后去掉 --dry 即为真实清理;建议每次真实清理前先演练。
六、评论导入:import 子命令
6.1 参数说明
import --help 实测输出(节选):
$ /opt/remark42/bin/remark42.linux-amd64 import --help
remark42 v1.18.0-13b2035c-20261011T191723
Usage:
remark42.linux-amd64 [OPTIONS] import [import-OPTIONS]
...
[import command options]
-f, --file= input file name
-p, --provider=[disqus|wordpress|commento] import format (default: disqus)
-s, --site= site name (default: remark)
[$SITE]
--admin-passwd= admin basic auth password [$ADMIN_PASSWD]
--timeout= timeout for the command run (default: 60m)
支持三种第三方格式:disqus(默认)、wordpress、commento。
6.2 Disqus 格式输入样例
按 Disqus 官方导出格式构造最小样例(1 个页面线程 + 1 条评论),存为 /opt/remark42/import-test/disqus-test.xml:
<?xml version="1.0" encoding="utf-8"?>
<disqus xmlns="http://disqus.com" xmlns:dsq="http://meta.disqus.com/ex/1.0">
<thread dsq:id="thread-01">
<link>http://10.89.57.2:8136/post-01.html</link>
<title>导入测试页</title>
<message>导入测试页内容</message>
<createdAt>2026-10-22T02:00:00Z</createdAt>
</thread>
<post dsq:id="post-1001">
<thread dsq:id="thread-01"></thread>
<message>这是来自 Disqus 格式导入的第一条测试评论</message>
<createdAt>2026-10-22T02:05:00Z</createdAt>
<author isAnonymous="false">
<username>dsquser1</username>
<name>导入用户甲</name>
<email>dsquser1@example.com</email>
</author>
<ipAddress>10.89.57.1</ipAddress>
<isSpam>false</isSpam>
</post>
</disqus>
6.3 实测:导入并回读
$ cd /opt/remark42 && set -a && source remark42.env && set +a
$ ./bin/remark42.linux-amd64 import -f /opt/remark42/import-test/disqus-test.xml -p disqus -s dtest
remark42 v1.18.0-13b2035c-20261011T191723
2026/10/22 02:41:00.816 [INFO] import /opt/remark42/import-test/disqus-test.xml (disqus), site dtest
2026/10/22 02:41:00.817 [INFO] completed, status=202, {"status":"import request accepted"}
导入异步执行,稍候用开放 API 回读验证(imported:true 是导入评论的标记):
$ curl -s "http://localhost:8131/api/v1/find?site=dtest&url=http://10.89.57.2:8136/post-01.html"
{"comments":[{"id":"post-1001","pid":"","text":"这是来自 Disqus 格式导入的第一条测试评论","user":{"name":"导入用户甲","id":"disqus_233890ff30d6cdeb0812f51f8e75b776587b18ae","picture":"","admin":false},"locator":{"site":"dtest","url":"http://10.89.57.2:8136/post-01.html"},"score":0,"vote":0,"time":"2026-10-22T02:05:00Z","imported":true}],"info":{"url":"http://10.89.57.2:8136/post-01.html","count":1,"count_left":0,"first_time":"2026-10-22T02:05:00Z","last_time":"2026-10-22T02:05:00Z"}}
6.4 排障实录:目标站点必须先在 SITE 中配置
首次尝试把评论导入一个服务未配置的站点 dtest 时,命令返回 202 受理,但导入在服务端失败。服务日志显示拒绝原因:
$ journalctl -u remark42 --since "3 minutes ago" --no-pager | grep -i "import failed"
2026/10/22 02:40:14.927 [WARN] import failed, site "dtest" not found
解决办法:把目标站点加入环境变量 SITE(逗号分隔多站点)并重启服务后重新导入:
$ sed -i 's/^SITE=mysite$/SITE=mysite,dtest/' /opt/remark42/remark42.env
$ systemctl restart remark42
$ systemctl is-active remark42
active
这与《remark42 1.18.0 配置参数详解》中 SITE 参数「多站点逗号分隔」的行为互相印证:remark42 的站点是白名单制的,数据写入前站点必须已声明。
七、URL 批量迁移:remap 子命令
站点改域名、调整文章路径后,评论挂在旧 URL 下无法展示,remap 按规则文件批量重写评论所属页面 URL。
7.1 规则文件格式
每行一条规则,空格分隔「旧 URL 新 URL」两列;旧 URL 支持结尾 * 做前缀通配。实测规则文件 /opt/remark42/import-test/remap-rules.txt:
http://10.89.57.2:8136/post-01.html http://10.89.57.2:8136/post-02.html
7.2 实测:URL 迁移与双向验证
对 6.3 节导入的 dtest 站点执行迁移:
$ cd /opt/remark42 && set -a && source remark42.env && set +a
$ ./bin/remark42.linux-amd64 remap -f /opt/remark42/import-test/remap-rules.txt -s dtest
remark42 v1.18.0-13b2035c-20261011T191723
2026/10/22 02:41:20.144 [INFO] start remap, site dtest, file with rules /opt/remark42/import-test/remap-rules.txt
2026/10/22 02:41:20.144 [INFO] completed, status=202, {"status":"convert request accepted"}
迁移同样异步,稍候双向验证——旧 URL 评论清空、新 URL 评论出现:
$ curl -s "http://localhost:8131/api/v1/find?site=dtest&url=http://10.89.57.2:8136/post-01.html" | head -c 200
{"comments":[],"info":{"count":0,"count_left":0,"first_time":"0001-01-01T00:00:00Z","last_time":"0001-01-01T00:00:00Z"}}
$ curl -s "http://localhost:8131/api/v1/find?site=dtest&url=http://10.89.57.2:8136/post-02.html" | head -c 400
{"comments":[{"id":"post-1001","pid":"","text":"这是来自 Disqus 格式导入的第一条测试评论","user":{"name":"导入用户甲","id":"disqus_233890ff30d6cdeb0812f51f8e75b776587b18ae","picture":"","admin":false},"locator":{"site":"dtest","url":"http://10.89.57.2:8136/post-02.html"},"score":0,"vote":0,"time":"2026-10-22T02:05:00Z","imported":true}],"info":{"url":"http://10.89.57.2:8136/po
八、头像存储迁移:avatar 子命令
remark42 的头像默认存文件系统(AVATAR_STORE_TYPE=fs,目录 /opt/remark42/var/avatars,实测内容为 02/、04/、95/ 三个两级哈希子目录),也可切到 bolt 单文件存储。avatar 子命令在 fs|bolt|uri 三种存储间迁移,主要参数:--src.type、--src.fs.path(fs 源路径,默认 ./var/avatars)、--src.bolt.file(bolt 源文件,默认 ./var/avatars.db)及对应的 --dst.* 系列。
实测把 fs 头像整体迁移到 bolt 文件:
$ cd /opt/remark42 && set -a && source remark42.env && set +a
$ ./bin/remark42.linux-amd64 avatar --src.type=fs --src.fs.path=/opt/remark42/var/avatars --dst.type=bolt --dst.bolt.file=/opt/remark42/var/avatars-migrated.db
remark42 v1.18.0-13b2035c-20261011T191723
2026/10/22 02:41:28.794 [INFO] migrate avatars from fs to bolt
2026/10/22 02:41:28.796 [INFO] completed, migrated avatars = 3
$ ls -la /opt/remark42/var/avatars-migrated.db
-rw------- 1 root root 32768 Oct 22 02:41 /opt/remark42/var/avatars-migrated.db
回显 migrated avatars = 3,与 fs 目录中的头像文件数一致。该命令不依赖运行中的服务,迁移后把 AVATAR_STORE_TYPE/AVATAR_BOLT_FILE 等参数改为目标存储并重启服务即可生效(参数含义见《remark42 1.18.0 配置参数详解》)。方向对调(--dst.type=fs)可从 bolt 迁回文件系统。
九、开放 API 实操
开放端点无需登录即可访问,是前端评论区与统计脚本的数据来源。本节所有请求的 Base URL 为 http://10.89.57.2:8131/api/v1。
9.1 端点总表
以下端点清单经 v1.18.0 源码路由与逐条请求双重核对,⚠ 标注本文实测项:
| 方法 | 路径 | 说明 | 本文实测 |
|---|---|---|---|
| GET | /ping | 健康检查 | ⚠ |
| GET | /config?site= | 站点运行时配置(前端初始化用) | ⚠ |
| GET | /find?site=&url= | 拉取某页面的评论树 | ⚠ |
| GET | /last/{limit}?site= | 全站最新 N 条评论 | ⚠ |
| GET | /count?site=&url= | 单页面评论数 | ⚠ |
| POST | /counts?site= | 批量页面评论数(请求体为 URL 数组) | ⚠ |
| GET | /list?site= | 站点全部有评论的页面清单 | ⚠ |
| GET | /id/{id}?site=&url= | 按 ID 取单条评论 | ⚠ |
| GET | /comments?site=&user= | 拉取某用户的历史评论 | ⚠ |
| GET | /info?site=&url= | 页面评论元信息(条数/首末时间) | ⚠ |
| GET | /rss/post?site=&url= | 单页面评论 RSS | ⚠ |
| GET | /rss/site?site=、/rss/reply?site=&user= | 全站 RSS、回复 RSS | — |
| GET | /img?url=、/picture/{user}/{id} | 图片代理、用户头像 | — |
9.2 服务健康检查与运行时配置
$ curl -s http://localhost:8131/api/v1/ping
pong
$ curl -s "http://localhost:8131/api/v1/config?site=mysite" | head -c 260
{"version":"v1.18.0-13b2035c-20261011T191723","edit_duration":600,"admin_edit":false,"min_comment_size":2,"max_comment_size":4096,"admins":["auth_anon_admin01"],"admin_email":"admin@example.org","auth_providers":["anonymous"],"anon_vote":true,"low_score":-3,"c
/ping 返回 pong 即服务存活,可直接用于监控探针(注意:健康检查端点是 /api/v1/ping)。/config 回显的是环境变量解析后的实际生效值(如 max_comment_size:4096 对应 MAX_COMMENT_SIZE=4096),核对配置是否生效时优先用它。
9.3 评论读取
以 mysite 站点的实测页面 http://10.89.57.2:8136/post-01.html(2 条评论)为例。
单页面评论树 /find(支持 sort=active|time|score|controversy 排序参数):
$ curl -s "http://localhost:8131/api/v1/find?site=mysite&url=http://10.89.57.2:8136/post-01.html&sort=active" \
| python3 -c "import json,sys; d=json.load(sys.stdin); print('count=',d['info']['count'],' first_id=',d['comments'][0]['id'] if d['comments'] else 'none')"
count= 2 first_id= cabcece9-360e-4f88-9feb-c10a8230cadf
页面清单与评论数:
$ curl -s "http://localhost:8131/api/v1/list?site=mysite"
[{"url":"http://10.89.57.2:8136/post-01.html","count":2,"count_left":0,"first_time":"2026-10-22T02:13:44.847599203Z","last_time":"2026-10-22T02:11:35.908299439Z"}]
$ curl -s "http://localhost:8131/api/v1/count?site=mysite&url=http://10.89.57.2:8136/post-01.html"
{"count":2,"locator":{"site":"mysite","url":"http://10.89.57.2:8136/post-01.html"}}
批量评论数 /counts:请求体必须是 URL 字符串数组(错误用法见 9.5 节):
$ curl -s -X POST "http://localhost:8131/api/v1/counts?site=mysite" -H "Content-Type: application/json" -d '["http://10.89.57.2:8136/post-01.html"]'
[{"url":"http://10.89.57.2:8136/post-01.html","count":2,"count_left":0,"first_time":"0001-01-01T00:00:00Z","last_time":"0001-01-01T00:00:00Z"}]
全站最新评论 /last/{limit}:
$ curl -s "http://localhost:8131/api/v1/last/5?site=mysite" | head -c 200
[{"id":"1fd7d1ec-efd3-40eb-b7ca-c3dd22529b23","pid":"","text":"<p>超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超�
按 ID 取单条评论 /id/{id}——必须同时带 site 与 url 两个参数(存储按 URL 分桶,缺 url 会报错,见 9.5 节):
$ curl -s "http://localhost:8131/api/v1/id/cabcece9-360e-4f88-9feb-c10a8230cadf?site=mysite&url=http://10.89.57.2:8136/post-01.html" | head -c 260
{"id":"cabcece9-360e-4f88-9feb-c10a8230cadf","pid":"","text":"<p>这是通过 API 发表的第一条测试评论,验证 remark42 评论闭环。</p>\n","orig":"这是通过 API 发表的第一条测试评论,验证 remark42 评论闭环。","user":{"name":
用户历史评论 /comments:
$ curl -s "http://localhost:8131/api/v1/comments?site=mysite&user=anonymous_514e6574a9504050ad8895e9f7da2f9f4eb3f4b6" | head -c 200
{"comments":[{"id":"1fd7d1ec-efd3-40eb-b7ca-c3dd22529b23","pid":"","text":"<p>超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超超�
页面元信息 /info:
$ curl -s "http://localhost:8131/api/v1/info?site=mysite&url=http://10.89.57.2:8136/post-01.html"
{"url":"http://10.89.57.2:8136/post-01.html","count":2,"count_left":0,"first_time":"2026-10-22T02:13:44.847599203Z","last_time":"2026-10-22T02:11:35.908299439Z"}
9.4 RSS 订阅
$ curl -s "http://localhost:8131/api/v1/rss/post?site=mysite&url=http://10.89.57.2:8136/post-01.html" | head -c 220
<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Remark42 comments</title>
<link>http://10.89.57.2:8136/post-01.html</link>
9.5 典型错误用法实录
实测中踩到的三个错误用法,回显照录:
① /counts 传对象体报错——请求体传 {"site":...,"urls":[...]} 会反序列化失败,site 只能放查询参数,请求体必须是纯数组:
$ curl -s -X POST "http://localhost:8131/api/v1/counts" -H "Content-Type: application/json" -d '{"site":"mysite","urls":["http://10.89.57.2:8136/post-01.html"]}'
{"code":6,"details":"can't get list of posts from request","error":"json: cannot unmarshal object into Go value of type []string"}
② /id/{id} 缺 url 参数报 no bucket——存储按页面 URL 分桶:
$ curl -s "http://localhost:8131/api/v1/id/cabcece9-360e-4f88-9feb-c10a8230cadf?site=mysite"
{"code":1,"details":"can't get comment by id","error":"no bucket in store"}
③ 受保护端点未认证返回 401:
$ curl -s -o /dev/null -w "%{http_code}\n" "http://localhost:8131/api/v1/user"
401
$ curl -s "http://localhost:8131/api/v1/vote/cabcece9-360e-4f88-9feb-c10a8230cadf?site=mysite&vote=1" -X PUT
Unauthorized
十、认证 API 实操
10.1 匿名登录获取 JWT
启用匿名登录(AUTH_ANONYMOUS=true)后,GET /auth/anonymous/login 用查询参数 user(用户名)与 aud(站点 ID)换取 JWT,JWT 经 Set-Cookie 下发:
$ curl -si "http://10.89.57.2:8131/auth/anonymous/login?user=apitester&aud=mysite" | grep -i "set-cookie"
Set-Cookie: JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJyZW1hcms0MiIsImF1ZCI6Im15c2l0ZSIsImV4cCI6MTc5MjYzNzg3Nyw...
后续 API 调用从 Cookie 中提取 JWT 并以 X-JWT 请求头传入。实测注意两点:① aud 必须是已配置站点;② JWT 默认有效期 5 分钟(AUTH_TTL_JWT 可调,本环境实测改为 15m),过期后需重新登录。
10.2 用户信息与个人数据导出
GET /user 返回当前用户信息。实测发现:所有认证端点都必须带 ?site= 查询参数(服务端校验 JWT 中的站点声明与参数一致),缺省会返回 Access denied:
$ curl -s -H "X-JWT: $JWT" "http://localhost:8131/api/v1/user?site=mysite"
{"name":"apitester","id":"anonymous_518661cd68ff532b5a8466a61bd6038915d69c75","picture":"http://10.89.57.2:8131/api/v1/avatar/506fccdd53c07d2c8082b1e6736ee0c5a2c768b5.image","admin":false,"site_id":"mysite"}
GET /userdata 导出当前用户的全部个人数据(GDPR 合规用),响应为 gzip 压缩的 JSON(含用户信息与全部评论):
$ curl -s -H "X-JWT: $JWT" "http://localhost:8131/api/v1/userdata?site=mysite" -o /tmp/d1userdata.gz -w "HTTP %{http_code} size=%{size_download}\n"
HTTP 200 size=341
$ zcat /tmp/d1userdata.gz | head -c 200
{"info": {"name":"apitester","id":"anonymous_518661cd68ff532b5a8466a61bd6038915d69c75","picture":"http://10.89.57.2:8131/api/v1/avatar/506fccdd53c07d2c8082b1e6736ee0c5a2c768b5.image","admin":false,"site_id":"mysite"}, "comments":[{"id":"1568f9df-2c53-4da1-9529-718632929689
10.3 发表评论与预览
POST /comment?site= 发表评论,请求体含 text 与 locator(站点+页面 URL):
$ curl -s -H "X-JWT: $JWT" -H "Content-Type: application/json" -X POST \
"http://localhost:8131/api/v1/comment?site=mysite" \
-d '{"text":"D1 运维文档实测评论,用于管理 API 演示","locator":{"site":"mysite","url":"http://10.89.57.2:8136/post-01.html"}}' \
| python3 -c "import json,sys; d=json.load(sys.stdin); print('id=',d['id'],' user=',d['user']['name'],' text=',d['text'][:30])"
id= 1568f9df-2c53-4da1-9529-718632929689 user= apitester text= <p>D1 运维文档实测评论,用于管理 API 演示</p>
POST /preview?site= 只渲染不落库,返回格式化后的 HTML:
$ curl -s -H "X-JWT: $JWT" -H "Content-Type: application/json" -X POST \
"http://localhost:8131/api/v1/preview?site=mysite" -d '{"text":"**加粗**预览文本"}'
<p><strong>加粗</strong>预览文本</p>
投票 PUT /vote/{id}?site=&vote=1、编辑 PUT /comment/{id}、评论长度限制(MIN_COMMENT_SIZE/MAX_COMMENT_SIZE)与限流行为已在《Ubuntu 22.04 安装 remark42 指南》与《remark42 1.18.0 配置参数详解》中实测,本篇不重复展开。
十一、管理 API 实操
管理端点挂在 /api/v1/admin/ 下,用 HTTP Basic 认证,账号固定为 admin、密码为环境变量 ADMIN_PASSWD(本环境 q1w2e3r4)。未带凭据访问返回 401(9.5 节已演示)。与认证端点相同,所有管理端点必须带 ?site= 参数。
11.1 端点总表
以下清单经 v1.18.0 源码路由与逐条请求双重核对,⚠ 标注本文实测项:
| 方法 | 路径 | 说明 | 本文实测 |
|---|---|---|---|
| GET | /admin/user/{userid}?site= | 查询用户信息 | ⚠ |
| PUT | /admin/user/{userid}?site=&block=1&ttl= | 封禁/解封用户 | ⚠ |
| DELETE | /admin/user/{userid}?site= | 硬删除用户全部评论 | — |
| PUT | /admin/verify/{userid}?site=&verified=1 | 设置/取消用户「已认证」标记 | ⚠ |
| GET | /admin/blocked?site= | 封禁用户列表 | ⚠ |
| PUT | /admin/pin/{id}?site=&url=&pin=1 | 评论置顶 | ⚠ |
| PUT | /admin/readonly?site=&url=&ro=1 | 页面评论只读 | ⚠ |
| PUT | /admin/title/{id}?site=&url= | 重置评论的页面标题 | ⚠ |
| DELETE | /admin/comment/{id}?site=&url= | 删除单条评论(软删除) | ⚠ |
| GET | /admin/export?site= | 全站数据导出(native JSON Lines) | ⚠ |
| GET | /admin/wait?site= | 查询导入/导出任务状态 | ⚠ |
| POST | /admin/import?site=&provider= | 导入(子命令 backup/restore/import 的底层通道) | ⚠ |
| POST | /admin/remap?site= | URL 重写(remap 子命令的底层通道) | ⚠ |
11.2 用户管理
以 10.3 节的用户 apitester(anonymous_518661cd68ff532b5a8466a61bd6038915d69c75)为对象。
查询用户信息(含 IP 哈希、封禁与认证状态):
$ curl -s -u admin:q1w2e3r4 "http://localhost:8131/api/v1/admin/user/anonymous_518661cd68ff532b5a8466a61bd6038915d69c75?site=mysite" | head -c 260
{"name":"apitester","id":"anonymous_518661cd68ff532b5a8466a61bd6038915d69c75","picture":"http://10.89.57.2:8131/api/v1/avatar/506fccdd53c07d2c8082b1e6736ee0c5a2c768b5.image","ip":"306455241a46620d8ca3b24b804c1a2f29c64419","admin":false,"verified":true,"site_id
设置「已认证」标记(评论区显示认证徽标):
$ curl -s -u admin:q1w2e3r4 -X PUT "http://localhost:8131/api/v1/admin/verify/anonymous_518661cd68ff532b5a8466a61bd6038915d69c75?site=mysite&verified=1"
{"user":"anonymous_518661cd68ff532b5a8466a61bd6038915d69c75","verified":true}
封禁用户 10 分钟(ttl 为 Go 时长格式;不带 ttl 即永久封禁,且服务会同时软删除该用户全部评论):
$ curl -s -u admin:q1w2e3r4 -X PUT "http://localhost:8131/api/v1/admin/user/anonymous_518661cd68ff532b5a8466a61bd6038915d69c75?site=mysite&block=1&ttl=10m"
{"block":true,"site_id":"mysite","user_id":"anonymous_518661cd68ff532b5a8466a61bd6038915d69c75"}
封禁列表与封禁后发评论的实际拦截:
$ curl -s -u admin:q1w2e3r4 "http://localhost:8131/api/v1/admin/blocked?site=mysite"
[{"id":"anonymous_518661cd68ff532b5a8466a61bd6038915d69c75","name":"apitester","time":"2026-10-22T02:54:24.016515538Z"}]
$ curl -s -o /dev/null -w "%{http_code}\n" -H "X-JWT: $JWT" -H "Content-Type: application/json" -X POST \
"http://localhost:8131/api/v1/comment?site=mysite" \
-d '{"text":"被封后不应成功","locator":{"site":"mysite","url":"http://10.89.57.2:8136/post-01.html"}}'
403
$ curl -s -H "X-JWT: $JWT" -H "Content-Type: application/json" -X POST \
"http://localhost:8131/api/v1/comment?site=mysite" \
-d '{"text":"被封后不应成功","locator":{"site":"mysite","url":"http://10.89.57.2:8136/post-01.html"}}'
{"code":7,"details":"user blocked","error":"rejected"}
解封(block=0,临时封禁到期也会自动解除):
$ curl -s -u admin:q1w2e3r4 -X PUT "http://localhost:8131/api/v1/admin/user/anonymous_518661cd68ff532b5a8466a61bd6038915d69c75?site=mysite&block=0"
{"block":false,"site_id":"mysite","user_id":"anonymous_518661cd68ff532b5a8466a61bd6038915d69c75"}
11.3 评论管理
置顶评论(置顶后 find 回读该评论 pin:true):
$ curl -s -u admin:q1w2e3r4 -X PUT "http://localhost:8131/api/v1/admin/pin/1568f9df-2c53-4da1-9529-718632929689?site=mysite&url=http://10.89.57.2:8136/post-01.html&pin=1"
{"id":"1568f9df-2c53-4da1-9529-718632929689","locator":{"site":"mysite","url":"http://10.89.57.2:8136/post-01.html"},"pin":true}
页面只读开关(开启后该页面禁止新评论):
$ curl -s -u admin:q1w2e3r4 -X PUT "http://localhost:8131/api/v1/admin/readonly?site=mysite&url=http://10.89.57.2:8136/post-01.html&ro=1"
{"locator":{"site":"mysite","url":"http://10.89.57.2:8136/post-01.html"},"read-only":true}
只读期间发评论被拒(403,code:8):
$ curl -s -o /dev/null -w "%{http_code}\n" -H "X-JWT: $JWT" -H "Content-Type: application/json" -X POST \
"http://localhost:8131/api/v1/comment?site=mysite" \
-d '{"text":"只读期不应成功","locator":{"site":"mysite","url":"http://10.89.57.2:8136/post-01.html"}}'
403
$ curl -s -H "X-JWT: $JWT" -H "Content-Type: application/json" -X POST \
"http://localhost:8131/api/v1/comment?site=mysite" \
-d '{"text":"只读期不应成功","locator":{"site":"mysite","url":"http://10.89.57.2:8136/post-01.html"}}'
{"code":8,"details":"old post, read-only","error":"rejected"}
关闭只读(ro=0;注意若页面因 READONLY_AGE 年龄转只读,管理 API 无法解除,见《remark42 1.18.0 配置参数详解》):
$ curl -s -u admin:q1w2e3r4 -X PUT "http://localhost:8131/api/v1/admin/readonly?site=mysite&url=http://10.89.57.2:8136/post-01.html&ro=0"
{"locator":{"site":"mysite","url":"http://10.89.57.2:8136/post-01.html"},"read-only":false}
重置评论页面标题(服务端会抓取页面 <title>;页面不可达时标题保持原值):
$ curl -s -u admin:q1w2e3r4 -X PUT "http://localhost:8131/api/v1/admin/title/1568f9df-2c53-4da1-9529-718632929689?site=mysite&url=http://10.89.57.2:8136/post-01.html"
{"id":"1568f9df-2c53-4da1-9529-718632929689","locator":{"site":"mysite","url":"http://10.89.57.2:8136/post-01.html"}}
软删除单条评论(文本清空并带 delete:true 标记,用户身份保留):
$ curl -s -u admin:q1w2e3r4 -X DELETE "http://localhost:8131/api/v1/admin/comment/1568f9df-2c53-4da1-9529-718632929689?site=mysite&url=http://10.89.57.2:8136/post-01.html"
{"id":"1568f9df-2c53-4da1-9529-718632929689","locator":{"site":"mysite","url":"http://10.89.57.2:8136/post-01.html"}}
$ curl -s "http://localhost:8131/api/v1/id/1568f9df-2c53-4da1-9529-718632929689?site=mysite&url=http://10.89.57.2:8136/post-01.html" | python3 -m json.tool | head -20
{
"id": "1568f9df-2c53-4da1-9529-718632929689",
"pid": "",
"text": "",
"user": {
"name": "apitester",
"id": "anonymous_518661cd68ff532b5a8466a61bd6038915d69c75",
"picture": "http://10.89.57.2:8131/api/v1/avatar/506fccdd53c07d2c8082b1e6736ee0c5a2c768b5.image",
"admin": false,
"verified": true,
"site_id": "mysite"
},
...
"delete": true
}
11.4 全站导出与任务状态
GET /admin/export 流式导出全站数据,内容格式与 backup 子命令产物一致(native JSON Lines):
$ curl -s -u admin:q1w2e3r4 "http://localhost:8131/api/v1/admin/export?site=mysite" | head -c 420
{"version":1,"users":[{"id":"anonymous_518661cd68ff532b5a8466a61bd6038915d69c75","blocked":{"status":false,"until":"0001-01-01T00:00:00Z"},"verified":true,"details":{"user_id":""}}],"posts":[]}
{"id":"cabcece9-360e-4f88-9feb-c10a8230cadf","pid":"","text":"<p>这是通过 API 发表的第一条测试评论,验证 remark42 评论闭环。</p>\n","orig":"这是通过 API 发表的第一条测试评论,验证 remark4
GET /admin/wait 查询最近的导入/导出任务是否完成:
$ curl -s -u admin:q1w2e3r4 "http://localhost:8131/api/v1/admin/wait?site=mysite"
{"site_id":"mysite","status":"completed"}
十二、注意事项
- 子命令导入导出均为异步:
backup/restore/import/remap的命令行回显只代表「服务端已受理」(202),实际完成情况要用GET /api/v1/admin/wait查询或直接回读数据验证,脚本化时不要把 202 当成功终点。 - 站点白名单前置:
import/restore/remap的目标站点必须已在SITE环境变量中声明,否则服务端静默失败(日志import failed, site "xxx" not found),命令行仍返回 202(见 6.4 节排障实录)。 - 认证与管理端点都必须带
?site=:JWT 中带有站点声明(aud),服务端校验查询参数与之一致,缺失或不匹配一律Access denied/401(见 10.2 节)。 - 写入限流:写操作(发评/投票/编辑)与开放读端点分别有独立限流器(默认 10 req/s,写侧可调
UPDATE_LIMITER),同源高频请求会得到 403 与You have reached maximum request limit.回显;脚本压测或批量操作时注意加间隔,遇到限流退避重试即可。 - 敏感操作不可逆:
DELETE /admin/user/{userid}为硬删除且不可恢复;不带ttl的永久封禁会连带软删除该用户全部评论;cleanup去掉--dry即真实删除——三类操作执行前务必先备份(第三章)。 - 页面标题抓取受私网限制:
import/restore后服务端会尝试抓取页面补标题,v1.18.0 内置 SSRF 防护,目标为私网地址时日志出现access to private address is not allowed,标题保持为空或原值;生产环境页面走公网域名时无此问题。 - 以服务账户运行运维命令:root 执行
backup产生的备份文件属主为 root,avatar迁移生成的 bolt 文件亦然;统一用sudo -u remark42执行可避免属主混乱。 - 单文件数据库的备份窗口:bolt 是单文件存储(
/opt/remark42/var/mysite.db),除backup外做文件级冷备需先停服务或确认无写入,热拷贝可能得到不一致副本。
十三、常见问题
13.1 子命令报 the required flags '--secret' and '--url' were not specified
$ cd /opt/remark42 && source remark42.env
$ ./bin/remark42.linux-amd64 backup
remark42 v1.18.0-13b2035c-20261011T191723
the required flags `--secret' and `--url' were not specified
原因:.env 文件每行是 KEY=value,source 后变量只是 shell 局部变量,未导出给子进程。解决:set -a && source remark42.env && set +a,或改用 env $(cat remark42.env | xargs) ./bin/remark42.linux-amd64 backup。
13.2 导入显示 202 但数据没进来
按 6.4 节排查:先 journalctl -u remark42 | grep -i "import failed" 看是否有 site "xxx" not found;再确认 SITE 已含目标站点并重启服务;最后用 GET /api/v1/admin/wait?site= 确认任务 completed 后回读数据。
13.3 调用认证/管理 API 返回 Access denied 或 401
依次检查:① 是否带 ?site= 参数(最常见,见 10.2 节);② 管理端点是否用 -u admin:<ADMIN_PASSWD>;③ JWT 是否过期(默认 5 分钟,重新登录换新);④ X-JWT 头的值是否为裸 JWT(不要带 JWT= 前缀)。
13.4 GET /api/v1/id/{id} 报 no bucket in store
该端点必须同时传 site 与 url,缺 url 时存储无法定位分页桶(见 9.5 节)。先从 /find 或 /list 拿到评论所属页面 URL 再查。
13.5 /counts 报 cannot unmarshal object into Go value of type []string
请求体格式错误:site 放查询参数,请求体是 URL 数组 ["url1","url2"],不是 JSON 对象(见 9.5 节)。
13.6 批量操作频繁返回 403 You have reached maximum request limit.
触发了写入限流器。降低请求频率、串行化批量脚本、或调大 UPDATE_LIMITER(默认 10)后重启服务。限流按请求来源 IP 计数,经反向代理部署时需配置 TRUSTED_PROXIES 以免全体访客共享代理 IP 的额度(参数见《remark42 1.18.0 配置参数详解》)。
13.7 restore 找不到备份文件
restore 默认按 userbackup-{{.SITE}}-{{.YYYYMMDD}}.gz 模板在 ./var/backup 找文件,与 backup 实际产物文件名(带时分秒)不匹配,且默认目录是相对路径。一律用 -f 指定备份文件的绝对路径(见 4.3 节)。
十四、参考链接
- 上游项目主页:https://remark42.com
- 上游源码仓库(umputun/remark42):https://github.com/umputun/remark42(本文实测版本 v1.18.0,2026-10-11 发布)
- 官方文档-备份与恢复:https://remark42.com/docs/backup/
- 官方文档-API:https://remark42.com/docs/api/
- 站内相关文档:remark42 1.18.0 介绍与选型指南、Ubuntu 22.04 安装 remark42 指南、remark42 1.18.0 配置参数详解