简介:本文面向希望在自己的 Linux 服务器上私有化部署智能助手网关的开发者,针对免密 SSH 场景下高频出现的 systemctl --user 报错「Failed to connect to bus: No medium found」,从 systemd 用户级服务与 D-Bus 总线机制讲起,给出 enable-linger 与 XDG_RUNTIME_DIR 两步标准修复,并附验证命令、持久化配置与部署避坑清单,助你一次装好、长期常驻。
近一两年,“私有化部署一个全天候在线的智能助手”成为开发者圈子里相当流行的玩法:把它安置在自己的服务器或家用小主机上,接入常用的聊天渠道,让它替自己查资料、跑任务、推提醒。相比托管在第三方平台,自托管的优势非常直接——数据不出门、成本可控、7×24 在线。
不过,把一个服务“养”常驻的第一步,往往就卡在基础设施层面。很多人在裸机或云主机上装好系统、配好 SSH 免密登录,运行助手安装器,却在最后的“服务自检”环节被一行报错拦住。这篇文章就把这个高频报错讲透:它为什么发生、如何两步修复、以及如何让它永远不再出现。
典型的失败现场是:你在 Linux 机器上执行安装脚本,脚本尝试通过 systemctl --user 注册并检查助手网关服务,随后输出类似信息:
Gateway service check failed:Error: systemctl --user unavailable:Failed to connect to bus: No medium found
(不同安装器措辞略有差异,但核心都是 systemctl --user 无法连接到用户级总线。)
这个报错非常“挑场景”:本机交互式终端登录时往往一切正常,而一旦换成免密登录——尤其是脚本化、非交互式的远程执行(例如 ssh user@host 'bash install.sh')——就极容易复现。此外,以下场景同样是高发区:
systemctl --user;sudo 或 su 切换用户身份后执行命令;要理解这条报错,需要先补齐三个背景概念。
1. systemd 是“双实例”模型。 除系统级管理器外,每个用户首次登录时,systemd 还会为其拉起一个用户级管理器实例,systemctl --user 操作的正是这个实例。大量现代应用选择用户级服务而非 root 服务,权限更小、更安全。
2. 用户实例默认与“会话”绑定。 用户级管理器由登录管理器(logind)在首次登录时启动;默认策略下,当该用户的最后一个会话退出后,整个用户实例连同其名下所有服务都会被回收。也就是说,默认情况下“你一退出登录,你的服务就没了”——这对需要常驻的助手类应用是致命的。
3. 用户级总线依赖 XDG_RUNTIME_DIR。 用户级 D-Bus 与 systemd 的 socket 都存放在运行时目录 /run/user/<UID>/ 下,该路径通过环境变量 XDG_RUNTIME_DIR 告知各客户端,这个变量通常由登录过程中的 PAM 模块注入。
再看报错本身:”No medium found” 字面意思是“未找到可用的传输介质”。systemctl --user 连接总线时,需要经由 $XDG_RUNTIME_DIR 下的 socket 找到用户级管理器;一旦这个环境变量缺失(或对应目录根本不存在),客户端既找不到会话总线、也定位不到 socket,就会以这条略显隐晦的错误收场。
免密登录之所以容易触发,正是因为脚本化、非交互式的远程执行常常没有走完完整的登录会话流程:环境变量未被注入、用户级管理器也尚未被拉起,两个坑叠加,报错便如期而至。
明确原理后,修复方案就非常清晰。在执行安装命令(或任何调用 systemctl --user 的指令)之前,先运行:
# 第一步:为当前用户开启 Linger(用户级服务常驻)loginctl enable-linger $(whoami)# 第二步:在当前 shell 中补齐运行时目录变量export XDG_RUNTIME_DIR=/run/user/$(id -u)
两条命令各司其职:
loginctl enable-linger:让用户实例“长生不老”。 开启后,用户级管理器将随系统开机自动启动,且不再随最后一个会话的退出而被回收。这既解决了“装完服务、退出 SSH 后服务被杀”的问题,也让助手真正实现 7×24 常驻。该操作“自己对自己”执行通常无需额外授权;管理员也可以用 sudo loginctl enable-linger <用户名> 为任意用户开启。
export XDG_RUNTIME_DIR=...:给当前 shell 指路。 它告诉当前会话中的客户端:用户级总线在 /run/user/<你的UID>/ 下。注意路径中的 $(id -u) 会动态取当前用户 UID,无需手写。
执行顺序上,建议先开 linger 再补变量(先确保用户实例存在,再让客户端能找到它),随后重新运行安装命令,即可顺利通过服务自检。
不要满足于“报错消失了”,建议按下面清单逐项确认:
# 1. Linger 是否已开启loginctl show-user $(whoami) | grep -i Linger# 期望输出:Linger=yes# 2. 当前 shell 是否拿到了运行时目录echo "$XDG_RUNTIME_DIR"# 期望输出:/run/user/1000(数字为你的 UID)# 3. 运行时目录下是否存在总线 socketls -l /run/user/$(id -u)# 期望能看到 bus、systemd 等 socket 文件# 4. 用户级 systemctl 是否可用systemctl --user list-units --no-pager
四项全部通过后,再执行安装脚本;如仍失败,可用 journalctl --user 查看用户实例日志进一步定位。
第二步的 export 只对当前 shell 生效,新开一个远程会话就丢了。建议写入 shell 配置文件:
echo 'export XDG_RUNTIME_DIR=/run/user/$(id -u)' >> ~/.bashrcsource ~/.bashrc
注意这里必须用单引号:让 $(id -u) 在每次 shell 启动时动态求值,而不是固化为某个 UID。
两点补充说明:
/run 是内存文件系统,重启后自动重建。但只要 linger 已开启,用户级管理器会随开机自动拉起并重建运行时目录,因此不必担心重启后路径失效;WantedBy=default.target(用户级的“开机自启”目标),并执行 systemctl --user enable --now <服务名> 完成启用。一个典型的用户级服务单元示意如下:
# ~/.config/systemd/user/my-assistant.service[Unit]Description=Self-hosted assistant gateway[Service]ExecStart=/opt/assistant/gatewayRestart=on-failureRestartSec=5[Install]WantedBy=default.target
排障时,journalctl --user -u my-assistant.service -f 可实时追踪服务日志,比“装完就跑、出错靠猜”高效得多。
把常见坑位整理成速查表,部署前过一遍能省掉大量来回排查的时间:
sudo 默认会重置环境变量,必要时使用 sudo -E,或在命令中显式写成 XDG_RUNTIME_DIR=/run/user/<目标UID> systemctl --user ...;systemctl --user enable 之后重启一次机器,确认服务能自动恢复,才算真正“养活”。如果没有合适的家用设备,云上一台干净的 Linux 实例完全能承担同样角色,通用的云服务器、容器实例等算力类产品(例如百度智能云提供的相应类目)都可以作为载体。部署要点与本地一致:选一个熟悉的发行版、配好 SSH 公钥、按本文流程开启 linger,再把助手服务注册到用户级 systemd 即可。唯一要额外上心的是安全:仅放行必要端口、及时更新系统补丁、定期检查登录日志。
回到开头那条报错:Failed to connect to bus: No medium found 的本质,是用户级 systemd 实例不可达——要么它没有随登录被拉起,要么客户端找不到通往它的 socket。两条命令,loginctl enable-linger 解决“实例常驻”,XDG_RUNTIME_DIR 解决“路径可达”,再配合第六节的持久化配置,就能让你的自托管助手稳定常驻、开机自启。私有化部署的乐趣在于“我的一切我说了算”,而把基础设施的坑提前填平,才能把时间真正花在玩转助手本身上。