Noctalia 剪切板历史丢失修复:KeepassXC 替代 gnome-keyring
现象
Niri + Noctalia(基于 quickshell 的桌面 shell)自带剪切板管理器,但每次重启后剪切板历史全部丢失。面板打开后历史为空,仿佛从未复制过任何内容。
诊断过程
1. 剪切板历史是加密持久化的,不是内存态
Noctalia 的剪切板历史保存在:
~/.local/state/noctalia/clipboard/
├── index.enc # 加密索引(477B,NOCTALIAENC1 头)
└── entries/ # 加密条目
└── <时间戳>-1.enc
文件头 NOCTALIAENC1 表明使用 libsodium 加密。历史数据其实一直没丢,只是解不开。
2. 解密需要一把 master key,存在 Secret Service 里
Noctalia 用 32 字节 master key 派生各用途密钥(crypto_kdf_derive_from_key,context 区分 clipboard / calendar)。这把 master key 通过 libsecret 存到 Secret Service(gnome-keyring),schema 为 dev.noctalia.Secret,属性:
application = noctalia
scope = storage
owner = encrypted-state
name = master-key
version = 1
3. 启动日志暴露真相
每次启动日志都出现:
[WRN] [storage-key] encrypted cache/history exists, but its storage master key is missing
对应源码 lookupKey() 的 NotFound 分支:加密数据存在,但 Secret Service 里查不到 master key → 进入 MissingKey 状态 → 历史无法解密,且新条目也不落盘(持久化禁用)→ 每次重启都"丢"。
4. 真正的根因:gnome-keyring 双实例 + 启动锁定
排查发现 master key 其实存在(用 secret-tool search 在 keyring 里找到了对应 item,md5 哈希属性完全匹配),但 noctalia 启动时读不到。原因有两层:
- gnome-keyring 双实例冲突:systemd user service(
gnome-keyring-daemon.service)与 dbus activation 实例(dbus-:1.1-org.freedesktop.secrets@0.service)并存,日志出现discover_other_daemon: 1、The Secret Service was already initialized。实际持有org.freedesktop.secrets的是 activation 实例。 - 默认密钥环启动时处于 Locked 状态:
org.freedesktop.Secret.Collection.Locked = true,noctalia 带SECRET_SEARCH_UNLOCK解锁失败 → 搜不到 → 误报 NotFound。
gnome-keyring 被卸载/重装、keyring 文件重建等历史操作(本机 7 月起反复出现该警告)加剧了死锁:数据在、key 在、但服务状态异常导致永远读不到。
修复方案:KeepassXC 替代 gnome-keyring
与其修复脆弱的 gnome-keyring,不如让 KeepassXC 作为 Secret Service 提供者(用户已有的密码管理器,支持 FdoSecrets 集成)。
步骤 1:备份 master key(关键保险)
趁 keyring 解锁窗口,把 key 导出为 hex 文件:
secret-tool lookup application noctalia scope storage \
owner encrypted-state name master-key version 1 \
| xxd -p -c 100 > ~/.local/state/noctalia/storage.key.bak
chmod 600 ~/.local/state/noctalia/storage.key.bak
即使后续方案失败,也可用 Noctalia 的 [storage] key_source = "file" 模式回退(key 文件要求 64 位小写 hex,可选换行)。
步骤 2:KeepassXC 启用 Secret Service 集成(两个开关!)
只开全局开关不够,新版 KeepassXC 要求每个数据库单独配置暴露组:
Settings → Secret Service Integration → Enable KeePassXC Freedesktop.org Secret Service integrationDatabase → Database Settings → Secret Service Integration → 勾选 Expose entries under this group 并选择组(否则该数据库不创建 collection,文档原话:“Collections are deleted if the exposed group is set to none”)
验证服务接管:
busctl --user list | grep org.freedesktop.secrets # owner 变成 keepassxc
gdbus call --session --dest org.freedesktop.secrets \
--object-path /org/freedesktop/secrets \
--method org.freedesktop.DBus.Properties.Get \
org.freedesktop.Secret.Service Collections
步骤 3:卸载 gnome-keyring,释放 D-Bus 名字
org.freedesktop.secrets 是独占名字,gnome-keyring 不退出 KeepassXC 抢不到。卸载后 keyring 数据文件(~/.local/share/keyrings/)保留在磁盘上,可随时回滚。
步骤 4:迁移 master key(踩坑记录)
用 gdbus 调 org.freedesktop.Secret.Service.CreateItem 会报 UnknownInterface——KeepassXC 的 Service 接口没有 CreateItem(只有 OpenSession/CreateCollection/SearchItems/Unlock/Lock/GetSecrets/ReadAlias/SetAlias)。
正确入口在 Collection 接口:
gdbus call --session \
--dest org.freedesktop.secrets \
--object-path /org/freedesktop/secrets/collection/<collection-id> \
--method org.freedesktop.Secret.Collection.CreateItem \
"{'org.freedesktop.Secret.Item.Label': <'Noctalia encrypted storage key'>, \
'org.freedesktop.Secret.Item.Attributes': <{'application': 'noctalia', 'scope': 'storage', \
'owner': 'encrypted-state', 'name': 'master-key', 'version': '1', \
'xdg:schema': 'dev.noctalia.Secret'}>}" \
"('', [], [bytearray([<32字节二进制>])])" \
"true"
要点:
- 必须带
xdg:schema: dev.noctalia.Secret属性:Noctalia 用自定义 schema 搜索,libsecret 会把 schema 作为xdg:schema一起匹配;用secret-tool store(Generic schema)存进去 Noctalia 搜不到。 - value 是二进制 32 字节:
gdbus解析嵌套(oayay)有缺陷(unknown keyword),可用 dbus-next 等库;KeepassXC 的 collection id 形如_E5_AF_86_E7_A0_81(URL 编码的"密码")。 - 密码字段存的是原始字节,不能手动在 GUI 里输 hex(会变成 64 字节 ASCII,Noctalia 校验
SecureKey::Size会报 invalid size)。
步骤 5:重启 Noctalia 验证
kill <noctalia-pid> && nohup noctalia &
验证点:
- 启动日志不再出现
storage master key is missing - 剪切板面板恢复历史条目
- 新复制的内容落盘到
entries/
降级场景(重要)
KeepassXC 数据库锁定时 FdoSecrets 不公开 collection,Noctalia 会回到 Unavailable 状态。Noctalia 内置了恢复机制:监听 org.freedesktop.secrets 名字出现 → retrySecretServiceConsumers() → 重新 lookup。所以解锁 KeepassXC 后 Noctalia 能自动恢复,无需重启。代价是保持 KeepassXC 常驻解锁。
关键命令速查
# 谁持有 Secret Service 名字
busctl --user list | grep org.freedesktop.secrets
# 查看服务/集合暴露了哪些接口
gdbus introspect --session --dest org.freedesktop.secrets --object-path /org/freedesktop/secrets
gdbus introspect --session --dest org.freedesktop.secrets --object-path /org/freedesktop/secrets/collection/<id>
# 读 master key(验证迁移)
secret-tool lookup application noctalia scope storage owner encrypted-state name master-key version 1
# 查看 Noctalia 日志中的 storage-key 告警
grep "storage-key" ~/.cache/noctalia/noctalia.log
注意事项
- 停用 gnome-keyring 会影响依赖它的应用:Chrome 密码(
Chrome Safe Storage)、GitHub 凭据、部分 Flatpak 应用密钥等。数据文件保留,但服务不提供,相关应用需重新登录。 - KeepassXC 的 Secret Service 是只读优先实现:Service 接口没有
CreateItem(Collection 接口有),写入能力有限,读取/搜索是完整支持的。 - 备份的
storage.key.bak等于历史内容的解密钥匙,务必 600 权限妥善保管。