Ubuntu 22.04 安装 Tinode 0.25.3 指南

Tinode 0.25.3 在 Ubuntu 22.04 上的从零安装实录:发行包下载与 sha256 校验、MySQL 8 数据库准备、keygen 生成 API key、init-db 初始化、前台运行与 systemd 托管两种形态、Web 托管与点对点消息闭环验证、升级与卸载,以及 ROOT 账号 401 与数据库连接失败的排障实录。全部命令与回显来自真实服务器实测。

Tinode即时通讯Tinode2026/10/26

Ubuntu 22.04 安装 Tinode 0.25.3 指南

目标:在一台 Ubuntu 22.04 服务器上从零安装 Tinode v0.25.3 即时消息服务端:准备 MySQL 数据库、下载并校验官方发行包、初始化数据库、以 systemd 托管服务,最后通过 Web 控制台与原生 REST/长轮询接口完成注册与点对点消息的功能闭环验证。本文所有命令与回显均来自真实服务器实测,可直接照做。

一、环境信息

本文的安装与验证在下列环境中完成,所有回显均可按此环境复核:

项目实测值
主机名tinode-sys
操作系统Ubuntu 22.04.5 LTS
数据库MySQL 8.0.46
服务端版本Tinode v0.25.3(发行包 tinode-alldbs.linux-amd64.tar.gz)
客户端端口6060(HTTP/WebSocket/长轮询 + Web 控制台)
gRPC 端口16060

环境快照实测回显:

hostname
head -2 /etc/os-release
mysql --version
tinode-sys
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
mysql  Ver 8.0.46-0ubuntu0.22.04.4 for Linux on x86_64 ((Ubuntu))

二、版本选型与安装方案

2.1 版本选型

Tinode 服务端以 GitHub Releases 发布预编译二进制,无 apt/yum 官方软件源。2026-10-26 查询 GitHub Releases,最新稳定版为 v0.25.3(发布于 2026-07-04)。本文选用 tinode-alldbs.linux-amd64.tar.gz 发行包:包名中的 alldbs 表示内置全部四种数据库适配器(MySQL、PostgreSQL、RethinkDB、MongoDB),Linux amd64 架构。若只需单一数据库适配器,Releases 页也提供 tinode-mysql.linux-amd64.tar.gz 等单适配器包,安装步骤完全一致。

2.2 运行形态方案对比

Tinode 是单进程二进制,运行形态有两种,本文两种都实测:

方案启动方式适用场景
前台直接运行./tinode -config tinode.conf快速验证、调试配置
systemd 托管systemctl start tinode生产常驻、开机自启(本文主线)

关键差异:前台方式日志直达终端、退出即停;systemd 方式获得崩溃自动拉起(Restart=on-failure)、开机自启与 journald 日志托管,生产环境应使用后者。数据库适配器的选择见 2.3 节。

2.3 数据库适配器选型

💡 技术拓展:Tinode 通过适配器层支持四种存储后端,发行包 alldbs 版本全部内置——

适配器特点适用场景
MySQLUbuntu/Debian 仓库直装,运维生态最普及中小规模自托管(本文采用)
PostgreSQL功能丰富的开源关系库已有 PG 运维体系的环境
RethinkDBTinode 历史默认后端,JSON 文档型与官方早期文档对齐的存量部署
MongoDB文档型分布式数据库已有 Mongo 集群的复用

本文因 Ubuntu 22.04 可用仓库直接安装 MySQL 8 且社区资料最多,实际采用 MySQL 适配器。适配器由配置文件 store_config.use_adapter 一项切换,切换后需重新初始化数据库(见第六章)。

三、安装准备

3.1 安装基础依赖

Tinode 二进制无任何运行时依赖,唯一需要预装的是数据库服务器与下载/校验工具。在全新系统上执行:

apt-get update && apt-get install -y mysql-server curl

完成后验证数据库可用(实测回显):

mysql --version
mysql  Ver 8.0.46-0ubuntu0.22.04.4 for Linux on x86_64 ((Ubuntu))

3.2 准备数据库账号

初始化程序 init-db 需要一个可建库的 MySQL 账号。以 root 走本机 socket 进入 MySQL(root 免密登录本机 socket 是 Ubuntu 打包默认行为),创建专用账号并授权:

mysql -e "CREATE USER IF NOT EXISTS 'tinode'@'localhost' IDENTIFIED BY 'q1w2e3r4'; GRANT ALL PRIVILEGES ON *.* TO 'tinode'@'localhost'; FLUSH PRIVILEGES;"
mysql -e "SELECT User,Host FROM mysql.user WHERE User='tinode';"
User	Host
tinode	localhost

说明:GRANT ALL PRIVILEGES ON *.* 是因为 init-db 要创建名为 tinode 的数据库;若遵循最小权限原则,可改为 GRANT ALL PRIVILEGES ON tinode.* TO 'tinode'@'localhost',但需先手工 CREATE DATABASE tinode;。本文为安装流畅采用全库授权。数据库本身不需要预建,init-db 检测到不存在时会自动创建(见 6.1 节回显)。

3.3 生成 API key 与 HMAC salt

Tinode 服务端对客户端请求校验 apikey 参数,密钥对由发行包内的 keygen 工具生成。运行(每次输出一对新值):

cd /opt/tinode-app && ./keygen
API key v1 seq1 [ordinary]: AQAAAAABAABb1lv6-dWoYXC4M6VO5gY9
HMAC salt: XxCVzrVjlX84Urtke1MuW+6DzuqchOYPkYlUgJAz+m8=

用法:把 HMAC salt 写入 tinode.conf 的 api_key_salt,把 API key 用在所有客户端请求的 apikey 参数上。两者必须配对:服务端用 salt 反推校验 key,换 salt 不换 key(或反之)会导致所有请求被拒。本环境部署时生成的 apikey 为 AQAAAAABAAAoeOI7tA3HsYvdzDhYhZJy,对应 conf 第 36 行的 salt(见 5.1 节回显),后文 REST 示例沿用这把 key。修改 salt 后必须重启服务才生效。

四、下载与部署发行包

4.1 下载发行包

从 GitHub Releases 下载 v0.25.3 的 Linux amd64 全适配器包。若服务器出海带宽有限,为 curl 指定 HTTP 代理(-x 参数;直连环境去掉该参数即可):

mkdir -p /opt/dl && cd /opt/dl
curl -sIL --http1.1 -x http://192.168.1.62:7897 https://github.com/tinode/chat/releases/download/v0.25.3/tinode-alldbs.linux-amd64.tar.gz | grep -iE 'HTTP/|content-length|content-type'
Content-Length: 0
HTTP/1.1 200 Connection established
HTTP/1.1 200 OK
Content-Length: 40920300
Content-Type: application/octet-stream

说明:HTTP/1.1 200 Connection established 是代理 CONNECT 阶段的回显,最后一个 Content-Length: 40920300 是真实文件大小(约 39 MB)。执行下载:

curl -x http://192.168.1.62:7897 -L -o tinode-alldbs.linux-amd64.tar.gz https://github.com/tinode/chat/releases/download/v0.25.3/tinode-alldbs.linux-amd64.tar.gz

实测下载约 2.6 秒完成(平均 14.7 MB/s),与上一步校验的 40920300 字节一致。

4.2 校验与解压

校验 sha256 并确认包内容:

sha256sum tinode-alldbs.linux-amd64.tar.gz
70635a0ca75da69708d5887e61fe9210e86be91d831e15828d0871e73c7a3b66  tinode-alldbs.linux-amd64.tar.gz
tar tzf tinode-alldbs.linux-amd64.tar.gz | head -6
./
./bob-128.jpg
./abcdef-128.jpg
./templ/
./xena-128.jpg
./init-db

包内为扁平结构:服务端二进制 tinode、初始化工具 init-db、密钥工具 keygen、示例数据装载脚本 credentials.sh 与 data.json、配置文件 tinode.conf、Web 控制台静态资源目录 static/、模板目录 templ/ 及若干示例头像图片。解压到部署目录:

mkdir -p /opt/tinode-app
tar -xzf tinode-alldbs.linux-amd64.tar.gz -C /opt/tinode-app
ls /opt/tinode-app
abc-128.jpg  carol-128.jpg  eve-128.jpg    static      tinode.conf
abcdef-128.jpg  chan-128.jpg  init-db     support-128.jpg  uploads
alice-128.jpg  credentials.sh  keygen     templ       xena-128.jpg
bf-128.jpg   data.json      tino-128.jpg   tinode
bob-128.jpg  dave-128.jpg   tinode.tar.gz

💡 说明:tinode.tar.gz 与 tinode.conf.bak 是本环境调试过程的自留文件,全新解压不会出现;uploads/ 是文件上传存储目录(首次有文件上传时自动使用)。核心三件套为 tinode(服务端)、init-db(数据库初始化)、tinode.conf(唯一配置文件)。

五、配置文件关键项

5.1 必改与默认项速览

tinode.conf 共约 700 行,JSON 格式,注释详尽。安装阶段必须核对的关键项实测如下:

grep -n '"listen"\|"grpc_listen"\|"static_mount"\|"api_key_salt"\|"uid_key"\|use_adapter' /opt/tinode-app/tinode.conf
9:	"listen": ":6060",
23:	"static_mount": "/",
28:	"grpc_listen": ":16060",
36:	"api_key_salt": "T713/rYYgW7g4m3vG6zGRh7+FM1t0T8j13koXScOAj4=",
249:		"uid_key": "6cTXIXgdxO8qxObF30+1hg==",
256:		"use_adapter": "mysql",

MySQL 连接段(本环境仅显式配置了用户与密码):

sed -n '/"mysql": {/,/}/p' /opt/tinode-app/tinode.conf | grep -E 'User|Passwd'
				"User": "tinode",
				"Passwd": "q1w2e3r4",

逐项说明:

  • listen(第 9 行):客户端 HTTP 入口监听地址与端口,默认 :6060,承载 JSON/WebSocket/长轮询三通道与 Web 控制台静态托管。
  • static_mount(第 23 行):Web 控制台静态资源的挂载路径,默认 /,即直接用服务端 6060 端口托管 Web 客户端,无需另起 Nginx。
  • grpc_listen(第 28 行):gRPC 通道监听端口,默认 :16060,供 tn-cli 与各语言 gRPC 客户端使用。
  • api_key_salt(第 36 行):apikey 校验盐,替换为 3.3 节 keygen 输出后重启生效。
  • uid_key(第 249 行):用户 UID 生成与解析密钥(16 字节 base64)。全新安装可保持预置值;一旦有用户数据,此值绝不可再改,否则既有 UID 无法解析。
  • use_adapter(第 256 行):数据库适配器选择,改为 mysql 使用 MySQL。
  • adapters.mysql.User / Passwd:数据库账号,对应 3.2 节创建的 tinode/q1w2e3r4;连接地址与库名沿用适配器默认值(本机实例、库 tinode)。

5.2 注册验证码的调试默认值

发行包 conf 中邮箱/手机验证器默认启用了调试回执,实测位置:

grep -n -B2 'debug_response' /opt/tinode-app/tinode.conf | head -6
456-				// REMOVE IN PRODUCTION!!! Otherwise anyone will be able to register
457-				// with fake emails.
458:				"debug_response": "123456"

含义:未配置真实邮件/短信出口时,注册阶段下发的验证码固定为 123456(第 498 行为手机号验证的同类默认值)。官方注释明确 REMOVE IN PRODUCTION——生产环境要么配置真实 SMTP 出口,要么删除这两处默认值,否则任何人都能用假邮箱注册。本文第八章的功能验证正是利用该默认值完成邮箱验证闭环。

六、初始化数据库

6.1 执行 init-db 并创建管理账号

init-db 负责建库、建表(schema 版本 116)并写入种子数据。执行:

cd /opt/tinode-app
./init-db -add_root admin:q1w2e3r4
2026/10/26 12:23:25 Database adapter: 'mysql'; version: 116
2026/10/26 12:23:25 Database not found. Creating.
2026/10/26 12:23:26 Database successfully created.
2026/10/26 12:23:26 No data provided, stopping
2026/10/26 12:23:26 ROOT user created: 'admin:q1w2e3r4'
2026/10/26 12:23:26 All done.

回显逐行解读:适配器与 schema 版本(mysql 116)→ 库不存在自动创建 → 未指定 -data 示例数据文件故跳过装载 → 创建 ROOT 特权账号 admin:q1w2e3r4 → 完成。

⚠️ ROOT 账号定位(实测重要发现):-add_root 创建的 admin 是服务端特权种子账号,供 tn-cli 管理宏等 root 级操作识别使用;它没有写入 basic(用户名+密码)登录命名空间,在 Web 控制台与 REST/长轮询接口上用 admin:q1w2e3r4 登录会返回 401(复现与结论见 10.1 节)。日常使用账号请通过注册接口创建(见 8.3 节),不要把 -add_root 当作「创建管理员登录账号」。

6.2 升级模式验证

schema 升级由 init-db -upgrade 完成。在当前已是最新 schema 的库上运行,可确认升级机制的判定逻辑(实测回显):

cd /opt/tinode-app && ./init-db -config tinode.conf -upgrade
2026/10/26 12:57:57 Database adapter: 'mysql'; version: 116
2026/10/26 12:57:57 Database exists, version is correct.
2026/10/26 12:57:57 All done.

七、启动与运行

7.1 方案 A:前台直接运行

cd /opt/tinode-app
./tinode -config /opt/tinode-app/tinode.conf

实测启动日志(输出节选):

I2026/10/26 13:00:00 Server v0.25:/opt/tinode-app/tinode:v0.25.3; pid 6193; 6 process(es)
I2026/10/26 13:00:00 DB adapter mysql 116
I2026/10/26 13:00:00 Push handlers configured: [stdout]
I2026/10/26 13:00:00 gRPC/1.82.0 server is registered at [:16060]
I2026/10/26 13:00:00 Serving static content from '/opt/tinode-app/static' at '/'
I2026/10/26 13:00:00 Listening for client HTTP connections on [:6060]

首行即版本自证:Server v0.25:/opt/tinode-app/tinode:v0.25.3。前台方式 Ctrl+C 即退出,验证通过后按 7.2 节改用 systemd 托管。

7.2 方案 B:systemd 托管

创建单元文件:

cat > /etc/systemd/system/tinode.service <<'EOF'
[Unit]
Description=Tinode instant messaging server
Documentation=https://tinode.co
After=network.target mysql.service
Wants=mysql.service

[Service]
Type=simple
WorkingDirectory=/opt/tinode-app
ExecStart=/opt/tinode-app/tinode -config /opt/tinode-app/tinode.conf
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now tinode
Created symlink /etc/systemd/system/multi-user.target.wants/tinode.service → /etc/systemd/system/tinode.service.

逐项说明:After/Wants mysql.service 保证数据库先于 Tinode 启动;Restart=on-failure + RestartSec=5 提供崩溃自愈;LimitNOFILE=65536 抬高句柄上限以支撑长连接数量。验证运行状态与自启(实测回显):

systemctl is-active tinode
systemctl is-enabled tinode
active
enabled

启动日志确认(journald 与前台输出同源,实测节选):

journalctl -u tinode --no-pager -n 10 | grep -E 'Server v0.25|Push handlers|gRPC|Serving static|Listening'
Oct 26 12:23:26 tinode-sys tinode[5744]: I2026/10/26 12:23:26 Push handlers configured: [stdout]
Oct 26 12:23:26 tinode-sys tinode[5744]: I2026/10/26 12:23:26 gRPC/1.82.0 server is registered at [:16060]
Oct 26 12:23:26 tinode-sys tinode[5744]: I2026/10/26 12:23:26 Serving static content from '/opt/tinode-app/static' at '/'
Oct 26 12:23:26 tinode-sys tinode[5744]: I2026/10/26 12:23:26 Listening for client HTTP connections on [:6060]

八、部署验证与功能闭环

8.1 Web 控制台验证

6060 端口同时托管 Web 控制台。浏览器访问 http://<服务器IP>:6060/ 应看到登录页;命令行验证静态托管(实测回显):

curl -sI http://localhost:6060/ | head -6
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: must-revalidate, public, max-age=39600
Content-Length: 2471
Content-Type: text/html; charset=utf-8
Last-Modified: Sat, 04 Jul 2026 14:40:58 GMT

HTTP 200 且 Content-Type: text/html 说明 Web 控制台已随服务端一并上线(Last-Modified 与 v0.25.3 发布日期一致)。若云服务器有防火墙/安全组,放行 TCP 6060(Web 与 JSON/WS/LP 通道)与 16060(gRPC)后即可从外部访问。

8.2 通过长轮询接口建会话

以下用 curl 直接驱动 Tinode 原生长轮询(LP)通道验证服务端 API。空体 POST 即创建会话,先看原始回显:

K="AQAAAAABAAAoeOI7tA3HsYvdzDhYhZJy"
B="http://localhost:6060/v0/channels/lp"
curl -s -X POST "$B?apikey=$K"
{"ctrl":{"params":{"sid":"aX06iiNwxZs"},"code":201,"text":"created","ts":"2026-10-26T13:04:32.448Z"}}

201 created,params.sid 即会话 ID。实际使用时把 sid 提取到变量,后续请求以 &sid=$SID 附加:

SID=$(curl -s -X POST "$B?apikey=$K" | grep -oE '"sid":"[^"]+"' | cut -d'"' -f4)

定义两个辅助函数(发送消息体、轮询服务端响应队列):

send(){ curl -s -X POST "$B?apikey=$K&sid=$SID" -H 'Content-Type: application/json' -d "$1"; }
poll(){ curl -s -X POST "$B?apikey=$K&sid=$SID" -H 'Content-Type: application/json'; echo; }

⚠️ 实测要点:send 必须显式带 Content-Type: application/json,否则服务端返回 400 malformed。LP 通道的语义是:不带消息体的 POST 建会话,带消息体的 POST 发包(无回显),空体 POST 逐包取回响应——因此每个请求后都要 poll 一次拿结果。

先发握手包并取回服务端能力描述(实测回显):

send '{"hi":{"id":"1","ver":"0.25","ua":"curl-lp/0.25.3"}}'; poll
{"ctrl":{"id":"1","params":{"build":"mysql:v0.25.3","maxFileUploadSize":8388608,"maxMessageSize":131072,"maxSubscriberCount":128,"maxTagCount":16,"maxTagLength":96,"minTagLength":2,"msgDelAge":600,"reqCred":{"auth":["email"]},"ver":"0.25"},"code":200,"text":"ok","ts":"2026-10-26T12:18:29.394Z"}}

reqCred:{"auth":["email"]} 表示本服务器策略要求注册时提供 email 凭据(与 5.2 节的验证码默认值联动)。

8.3 注册两个用户并完成点对点消息闭环

注册用户 tina。注意 acc 包必须带 "user":"new"(告诉服务端这是建新账号而非改当前账号)、scheme:basic、以及 base64 的 用户名:密码 secret(实测回显):

send '{"acc":{"id":"2","user":"new","scheme":"basic","username":"tina","secret":"'"$(echo -n 'tina:q1w2e3r4' | base64 -w0)"'","login":true,"cred":[{"meth":"email","val":"tina@example.com"}]}}'; poll
{"ctrl":{"id":"2","params":{"authlvl":"auth","cred":["email"],"desc":{"created":"2026-10-26T12:18:29.413Z","updated":"2026-10-26T12:18:29.413Z","defacs":{"auth":"JRWPASD","anon":"N"}},"expires":"2026-11-09T12:18:29.466Z","token":"8Y/EkEKfpugVuvFqFAABAAAAQvhB+rA5MyV/S4WOCW7LA6p1I96hHx0jQAzuu/ZYDt0=","user":"usr8Y_EkEKfpug"},"code":300,"text":"validate credentials","ts":"2026-10-26T12:18:29.41Z"}}

返回 300 validate credentials:账号已建(UID usr8Y_EkEKfpug)但需完成邮箱验证。用 5.2 节的调试验证码 123456 携带在 login 包中完成验证并登录(实测回显):

send '{"login":{"id":"3","scheme":"basic","secret":"'"$(echo -n 'tina:q1w2e3r4' | base64 -w0)"'","cred":[{"meth":"email","resp":"123456"}]}}'; poll
{"ctrl":{"id":"3","params":{"authlvl":"auth","expires":"2026-11-09T12:18:29.531Z","token":"8Y/EkEKfpugVuvFqFAABAAEAnYMLpmuWIaVG4LSgn77ADpt2BgKOpQ2GZXKGV4X7n+k=","user":"usr8Y_EkEKfpug"},"code":200,"text":"ok","ts":"2026-10-26T12:18:29.481Z"}}

200 ok 即登录成功。按同样步骤在第二个会话注册 derek(会话新建后重发 hi 再 acc/login,过程相同),得到 DEREK_UID=usrx-2oWQr-x2w。为 tina 设置公开资料(实测回显):

send '{"set":{"id":"5","topic":"me","desc":{"public":{"fn":"Tina Wang"}}}}'; poll
{"ctrl":{"id":"5","topic":"me","code":200,"text":"ok","ts":"2026-10-26T12:18:29.727Z"}}

tina 订阅 derek 的用户话题并发送点对点消息(实测回显):

send '{"sub":{"id":"7","topic":"usrx-2oWQr-x2w"}}'; poll
send '{"pub":{"id":"8","topic":"usrx-2oWQr-x2w","head":{"mime":"text/plain"},"content":"你好 derek,这是 Tinode 部署完成后的第一条点对点消息"}}'; poll
{"ctrl":{"id":"7","topic":"usrx-2oWQr-x2w","params":{"acs":{"want":"JRWPAD","given":"JRWPASD","mode":"JRWPAD"}},"code":200,"text":"ok","ts":"2026-10-26T12:18:29.758Z"}}
{"ctrl":{"id":"8","topic":"usrx-2oWQr-x2w","params":{"seq":1},"code":202,"text":"accepted","ts":"2026-10-26T12:18:29.77Z"}}

derek 侧订阅 tina 并拉取历史消息,收到 tina 刚发送的消息(实测回显):

send '{"sub":{"id":"4","topic":"usr8Y_EkEKfpug"}}'; poll
send '{"get":{"id":"5","topic":"usr8Y_EkEKfpug","what":"data","data":{"limit":3}}}'; poll
{"ctrl":{"id":"4","topic":"usr8Y_EkEKfpug","code":200,"text":"ok","ts":"2026-10-26T12:18:29.783Z"}}
{"data":{"topic":"usr8Y_EkEKfpug","from":"usr8Y_EkEKfpug","ts":"2026-10-26T12:18:29.77Z","seq":1,"head":{"mime":"text/plain"},"content":"你好 derek,这是 Tinode 部署完成后的第一条点对点消息"}}
{"ctrl":{"id":"5","topic":"usr8Y_EkEKfpug","params":{"count":1,"what":"data"},"code":208,"text":"delivered","ts":"2026-10-26T12:18:29.801Z"}}

至此闭环成立:注册(300→200)→ 资料 → 订阅(200)→ 发送(202 seq:1)→ 对端拉取(data 包 + 208 delivered)。Web 控制台上以 tina/derek 登录同样能看到这条消息,说明 REST 通道与 Web 客户端共享同一数据面。

九、升级、降级与多版本管理

9.1 升级

升级路径:备份 → 下载新版发行包解压到新目录 → init-db -upgrade 迁移 schema → 切换 systemd 指向 → 重启。schema 迁移命令与「已是最新」时的判定回显见 6.2 节(Database exists, version is correct.)。操作顺序上必须先备份 MySQL(mysqldump tinode > tinode-backup.sql)再执行 upgrade,schema 版本由 init-db 自动比对推进。

9.2 降级与多版本

Tinode 官方未提供 schema 降级工具,数据库一旦升级到新版 schema,旧版服务端无法识别,因此降级的唯一安全做法是用升级前的 mysqldump 备份恢复数据库后运行旧版二进制。多版本并存推荐目录隔离 + 软链切换(实测命令与回显):

ln -sfn /opt/tinode-app /opt/tinode-latest
readlink /opt/tinode-latest
/opt/tinode-app

新版本解压到 /opt/tinode-app-<新版本> 后,将软链重新指向新目录并 systemctl restart tinode 即完成切换;回滚时把软链改回旧目录即可(前提是数据库 schema 兼容旧版)。

十、常见问题与排障实录

10.1 init-db 创建的 admin 登录返回 401

现象:./init-db -add_root admin:q1w2e3r4 回显 ROOT user created,但在登录接口用 admin:q1w2e3r4 登录失败。实测复现:

send '{"login":{"id":"2","scheme":"basic","secret":"'"$(echo -n 'admin:q1w2e3r4' | base64 -w0)"'"}}'; poll
{"ctrl":{"id":"2","code":401,"text":"authentication failed","ts":"2026-10-26T12:59:59.234Z"}}

定位:ROOT 是服务端特权种子账号,写入的存储记录不带 basic(用户名+密码)登录命名空间,供服务端内部特权操作识别(如 tn-cli 标注「requires root privileges」的管理宏);客户端 basic 登录查找的是 basic 命名空间记录,自然 401。

解决:ROOT 账号不是给终端登录用的。日常账号通过注册接口创建(8.3 节的 acc+验证流程),或直接在 Web 控制台注册;确需管理特权时,用普通账号执行 tn-cli 管理命令的前提是账号具备 ROOT 权限级别,可由具备权限的账号通过 tn-cli 的 usermod 系列宏调整。

10.2 服务启动失败:Access denied for user

现象:把 MySQL 密码字段名误写成 "Password"(正确为 "Passwd")后,服务反复重启失败。journald 实测回显:

Oct 26 12:52:25 tinode-sys tinode[5975]: I2026/10/26 12:52:25 DB adapter mysql 116
Oct 26 12:52:25 tinode-sys tinode[5975]: E2026/10/26 12:52:25 Failed to connect to DB: Error 1045 (28000): Access denied for user 'tinode'@'localhost' (using password: NO)
Oct 26 12:52:25 tinode-sys systemd[1]: tinode.service: Main process exited, code=exited, status=1/FAILURE
Oct 26 12:52:25 tinode-sys systemd[1]: tinode.service: Failed with result 'exit-code'.

定位:tinode.conf 的 JSON 解析对未知字段名不报错,Password 被静默忽略后等于「密码为空」,于是 MySQL 拒绝连接——错误行的 using password: NO 正是空密码特征。同类问题还有 uid_key 被改成非法 base64(实测回显为 Failed to connect to DB: store: failed to parse config: illegal base64 data at input byte 32({... 后跟整段 store_config 回显)。

解决:字段名逐字符对照发行包原版 conf(Passwd、uid_key 为 16 字节 base64);改正后 systemctl restart tinode,以 systemctl is-active tinode 回显 active 与启动日志中的 Listening for client HTTP connections on [:6060] 为恢复判据。

10.3 LP 请求返回 400 malformed

现象:8.2 节的 send 若漏掉 -H 'Content-Type: application/json',服务端直接返回 400 malformed,且该会话后续包全部被拒。

解决:LP 通道所有带消息体的 POST 都必须携带 JSON Content-Type;空体轮询请求则不要带消息体。这是 curl 手工调试 Tinode 时最高频的坑。

十一、卸载方式

停止并取消自启(实测回显):

systemctl disable --now tinode
systemctl is-active tinode
systemctl is-enabled tinode
Removed /etc/systemd/system/multi-user.target.wants/tinode.service.
inactive
disabled

确认不再需要数据后,删除程序目录、数据目录与数据库(不可逆操作,执行前务必确认已备份):

rm -rf /opt/tinode-app /opt/dl /etc/systemd/system/tinode.service
systemctl daemon-reload
mysql -e "DROP DATABASE IF EXISTS tinode; DROP USER IF EXISTS 'tinode'@'localhost';"

若只是停用而非卸载,本文环境在演示后已按相反顺序恢复(实测回显):

systemctl enable --now tinode
systemctl is-active tinode
systemctl is-enabled tinode
Created symlink /etc/systemd/system/multi-user.target.wants/tinode.service → /etc/systemd/system/tinode.service.
active
enabled

十二、参考链接