版本信息
halo CLI: 1.3.0 (linux-x64, node v25.6.0)
@napi-rs/keyring: 1.3.0
OS: Debian 12 (Linux 6.8.0), 非桌面/headless 环境
问题描述
halo auth login 登录成功,halo auth current 也能显示 profile,但执行任何需要凭据的命令(如 halo post list)时报错:
Failed to read credentials for profile "blog": Couldn't access platform storage: PermissionDenied
Caused by:
PermissionDenied
复现步骤
1. 登录 — 成功
halo auth login --profile blog --url https://xxx.com --auth-type bearer --token pat_xxx
2. 验证 profile — 成功
halo auth current
输出正常的 profile 信息
3. 执行任何需要 auth 的命令 — 失败
halo post list
→ PermissionDenied
根因分析
halo CLI 通过 @napi-rs/keyring(底层 Rust keyring-rs)管理凭据
Linux 上 keyring-rs 优先通过 DBus secret service 读写凭据,这需要一个运行中的 secret service 守护进程(如 gnome-keyring-daemon)
在 headless / CI / 容器等非桌面环境中,没有 secret service,直接抛出 PermissionDenied,并没有降级到文件存储后端
矛盾的是:登录时 token 已经写入了 ~/.config/halo/keyring.json,文件存在且可读。但 keyring-rs 读取时绕过文件,坚持走 DBus secret service
为什么影响大
这不仅是极端情况。以下场景都会触发:
headless 服务器(最常见的使用场景 — 博客服务器通常没桌面)
Docker 容器
CI/CD 流水线
SSH 会话(DBUS_SESSION_BUS_ADDRESS 未正确继承)
WSL
本质上,用 CLI 管理博客的人大概率是连服务器操作,而这恰恰是 secret service 最不可能存在的环境。
建议修复方向
短期:加 --token 参数 / HALO_TOKEN 环境变量
允许用户直接传 token,完全绕过 keyring
最低侵入性,Headless 场景首选
中期:完善文件后端降级
keyring-rs 已经能写 keyring.json,读取时也应能直接读这个文件
或者 halo CLI 层做 fallback:DBus 失败 → 自己读 keyring.json
长期:换用纯文件存储(如 ~/.config/halo/credentials.json)
抛弃对系统 keyring 的依赖
加文件权限检查(chmod 600)保证基本安全
类似 kubectl、docker CLI 的做法
临时绕过方案
// 手动读 keyring.json 拿 token
const fs = require('fs');
const keyring = JSON.parse(fs.readFileSync('/root/.config/halo/keyring.json', 'utf8'));
const token = JSON.parse(keyring['@halo-dev/cli/profile:blog']).token;
// 然后用 token 直接调 REST API
版本信息
halo CLI: 1.3.0 (linux-x64, node v25.6.0)
@napi-rs/keyring: 1.3.0
OS: Debian 12 (Linux 6.8.0), 非桌面/headless 环境
问题描述
halo auth login 登录成功,halo auth current 也能显示 profile,但执行任何需要凭据的命令(如 halo post list)时报错:
Failed to read credentials for profile "blog": Couldn't access platform storage: PermissionDenied
Caused by:
PermissionDenied
复现步骤
1. 登录 — 成功
halo auth login --profile blog --url https://xxx.com --auth-type bearer --token pat_xxx
2. 验证 profile — 成功
halo auth current
输出正常的 profile 信息
3. 执行任何需要 auth 的命令 — 失败
halo post list
→ PermissionDenied
根因分析
halo CLI 通过 @napi-rs/keyring(底层 Rust keyring-rs)管理凭据
Linux 上 keyring-rs 优先通过 DBus secret service 读写凭据,这需要一个运行中的 secret service 守护进程(如 gnome-keyring-daemon)
在 headless / CI / 容器等非桌面环境中,没有 secret service,直接抛出 PermissionDenied,并没有降级到文件存储后端
矛盾的是:登录时 token 已经写入了 ~/.config/halo/keyring.json,文件存在且可读。但 keyring-rs 读取时绕过文件,坚持走 DBus secret service
为什么影响大
这不仅是极端情况。以下场景都会触发:
headless 服务器(最常见的使用场景 — 博客服务器通常没桌面)
Docker 容器
CI/CD 流水线
SSH 会话(DBUS_SESSION_BUS_ADDRESS 未正确继承)
WSL
本质上,用 CLI 管理博客的人大概率是连服务器操作,而这恰恰是 secret service 最不可能存在的环境。
建议修复方向
短期:加 --token 参数 / HALO_TOKEN 环境变量
允许用户直接传 token,完全绕过 keyring
最低侵入性,Headless 场景首选
中期:完善文件后端降级
keyring-rs 已经能写 keyring.json,读取时也应能直接读这个文件
或者 halo CLI 层做 fallback:DBus 失败 → 自己读 keyring.json
长期:换用纯文件存储(如 ~/.config/halo/credentials.json)
抛弃对系统 keyring 的依赖
加文件权限检查(chmod 600)保证基本安全
类似 kubectl、docker CLI 的做法
临时绕过方案
// 手动读 keyring.json 拿 token
const fs = require('fs');
const keyring = JSON.parse(fs.readFileSync('/root/.config/halo/keyring.json', 'utf8'));
const token = JSON.parse(keyring['@halo-dev/cli/profile:blog']).token;
// 然后用 token 直接调 REST API