Skip to content

Commit 5c543af

Browse files
authored
Merge pull request #27 from LobsterTrap/fix/podman-socket-error-message
fix(bootstrap): improve error message when Podman socket is not active
2 parents 73909fb + 32487e0 commit 5c543af

3 files changed

Lines changed: 179 additions & 39 deletions

File tree

crates/openshell-bootstrap/src/container_runtime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ fn current_uid() -> Option<u32> {
228228
}
229229

230230
/// Check whether a binary is on PATH.
231-
fn has_binary(name: &str) -> bool {
231+
pub(crate) fn has_binary(name: &str) -> bool {
232232
std::process::Command::new(name)
233233
.arg("--version")
234234
.stdout(std::process::Stdio::null())

crates/openshell-bootstrap/src/docker.rs

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -315,11 +315,36 @@ fn runtime_not_reachable_error(
315315
"No container runtime socket found and {} is not set.",
316316
host_env
317317
));
318-
hints.push(format!(
319-
"Install and start {}. See the support matrix \
320-
in the OpenShell docs for tested configurations.",
321-
runtime.display_name()
322-
));
318+
// Check if the binary is installed but the socket is missing —
319+
// this typically means the systemd socket unit needs activation.
320+
if crate::container_runtime::has_binary(runtime.binary_name()) {
321+
match runtime {
322+
ContainerRuntime::Podman => {
323+
hints.push(
324+
"Podman is installed but its API socket is not active.\n\n \
325+
Enable the Podman socket with:\n\n \
326+
sudo systemctl enable --now podman.socket\n\n \
327+
For rootless Podman, run as your regular user:\n\n \
328+
systemctl --user enable --now podman.socket"
329+
.to_string(),
330+
);
331+
}
332+
ContainerRuntime::Docker => {
333+
hints.push(
334+
"Docker is installed but its daemon is not running.\n\n \
335+
Start Docker with:\n\n \
336+
sudo systemctl start docker"
337+
.to_string(),
338+
);
339+
}
340+
}
341+
} else {
342+
hints.push(format!(
343+
"Install and start {}. See the support matrix \
344+
in the OpenShell docs for tested configurations.",
345+
runtime.display_name()
346+
));
347+
}
323348
} else {
324349
// Found sockets for possibly a different runtime
325350
let socket_list: Vec<String> = alt_sockets
@@ -1508,6 +1533,43 @@ mod tests {
15081533
);
15091534
}
15101535

1536+
#[test]
1537+
fn runtime_not_reachable_error_podman_socket_hint() {
1538+
// Verify that the Podman error message always contains the
1539+
// verification hint regardless of system state. Branch-specific
1540+
// assertions (socket hints, env var hints, systemctl suggestions)
1541+
// are avoided because other tests in this module mutate env vars
1542+
// concurrently, making branch selection non-deterministic.
1543+
//
1544+
// The new "binary installed but no socket" hint path is validated
1545+
// by code inspection: `has_binary()` is called and the Podman
1546+
// branch emits "systemctl enable --now podman.socket".
1547+
let err = runtime_not_reachable_error(
1548+
ContainerRuntime::Podman,
1549+
"no Podman socket found at standard locations",
1550+
"Failed to connect to Podman",
1551+
);
1552+
let msg = format!("{err:?}");
1553+
1554+
assert!(
1555+
msg.contains("Failed to connect to Podman"),
1556+
"should include the summary: {msg}"
1557+
);
1558+
assert!(
1559+
msg.contains("no Podman socket found"),
1560+
"should include the raw error detail: {msg}"
1561+
);
1562+
assert!(
1563+
msg.contains("podman info"),
1564+
"should always suggest 'podman info' verification: {msg}"
1565+
);
1566+
// The message must never be empty or missing help text
1567+
assert!(
1568+
msg.contains("help:") || msg.contains("Verify"),
1569+
"should include actionable help text: {msg}"
1570+
);
1571+
}
1572+
15111573
#[test]
15121574
fn runtime_not_reachable_error_with_env_var() {
15131575
// Simulate: DOCKER_HOST is set but daemon unresponsive.

docs/get-started/install-podman-linux.md

Lines changed: 111 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -103,20 +103,6 @@ Podman 4.0 or later is required.
103103

104104
Rootless Podman runs containers without root privileges. This is the default mode on Fedora and RHEL 9+ and the recommended configuration for desktops and laptops.
105105

106-
### Enable the Podman Socket
107-
108-
OpenShell communicates with Podman through a Unix socket. Enable the rootless socket for your user:
109-
110-
```console
111-
$ systemctl --user enable --now podman.socket
112-
```
113-
114-
Verify the socket exists:
115-
116-
```console
117-
$ ls $XDG_RUNTIME_DIR/podman/podman.sock
118-
```
119-
120106
### Configure Cgroup Delegation
121107

122108
OpenShell runs an embedded k3s cluster inside the gateway container. k3s needs to manage cgroup controllers for pod resource isolation. Without cgroup delegation, the gateway fails to start.
@@ -140,41 +126,41 @@ $ cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/cgroup.subtree_control
140126

141127
The output should include `cpuset cpu io memory pids`.
142128

143-
### Enable Login Lingering (Headless Systems)
129+
### Headless Systems Prerequisites
144130

145131
:::{note}
146-
**Headless/SSH systems only.** Skip this step on desktops and laptops where you log in directly. On those systems, your user session persists and systemd user services stay running.
132+
**Headless/SSH systems only.** If you log in directly at a desktop session, skip ahead to [Enable the Podman Socket](#enable-the-podman-socket). Desktop sessions handle login lingering, `XDG_RUNTIME_DIR`, and the D-Bus session bus automatically via PAM.
147133
:::
148134

149-
On headless systems accessed only over SSH, systemd terminates all user services when the last SSH session disconnects. This kills the Podman socket and any running gateway container. Login lingering tells systemd to keep your user services alive regardless of active sessions:
135+
On headless systems (accessed only over SSH, or when switching users with `su`/`sudo su`), three things must be configured before `systemctl --user` will work. Complete these steps in order.
136+
137+
**Enable login lingering.** Without lingering, systemd does not start a user session manager for your account. This means there is no D-Bus session bus, no user socket directory, and `systemctl --user` cannot function:
150138

151139
```console
152-
$ loginctl enable-linger $USER
140+
$ sudo loginctl enable-linger $USER
153141
```
154142

155-
Verify:
143+
Verify lingering is active:
156144

157145
```console
158146
$ loginctl show-user $USER --property=Linger
159147
Linger=yes
160148
```
161149

162-
### Verify XDG_RUNTIME_DIR (Headless Systems)
163-
164-
:::{note}
165-
**Headless/SSH systems only.** Desktop sessions set `XDG_RUNTIME_DIR` automatically via PAM. This step is only needed if you access the system exclusively over SSH.
166-
:::
150+
Lingering also keeps your user services alive when all SSH sessions disconnect, which prevents the Podman socket and gateway container from being killed.
167151

168-
Some SSH configurations do not set `XDG_RUNTIME_DIR`, which prevents the rootless Podman socket from being found. Check whether it is set:
152+
**Set XDG_RUNTIME_DIR and DBUS_SESSION_BUS_ADDRESS.** `systemctl --user` needs these two environment variables to find the user session bus. Direct SSH logins with PAM enabled set them automatically. If you access the system via `su -` or `sudo su`, they are not set. Check whether they exist:
169153

170154
```console
171155
$ echo $XDG_RUNTIME_DIR
156+
$ echo $DBUS_SESSION_BUS_ADDRESS
172157
```
173158

174-
If the output is empty, add the following to your `~/.bashrc` (or equivalent shell profile):
159+
If either is empty, add the following to `~/.bashrc` (or equivalent shell profile):
175160

176161
```bash
177162
export XDG_RUNTIME_DIR=/run/user/$(id -u)
163+
export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus
178164
```
179165

180166
Then reload:
@@ -183,6 +169,26 @@ Then reload:
183169
$ source ~/.bashrc
184170
```
185171

172+
:::{warning}
173+
`DBUS_SESSION_BUS_ADDRESS` references `$XDG_RUNTIME_DIR` in its value. You must set `XDG_RUNTIME_DIR` first. If you run `export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus` when `XDG_RUNTIME_DIR` is empty, the path expands to `unix:path=/bus`, which is invalid. Always source both exports together (e.g., `source ~/.bashrc`), or set `XDG_RUNTIME_DIR` on its own line before `DBUS_SESSION_BUS_ADDRESS`.
174+
175+
Login lingering must also be enabled before these variables are useful. The bus socket at `$XDG_RUNTIME_DIR/bus` is only created when systemd starts the user session manager, which requires lingering.
176+
:::
177+
178+
### Enable the Podman Socket
179+
180+
OpenShell communicates with Podman through a Unix socket. Enable the rootless socket for your user:
181+
182+
```console
183+
$ systemctl --user enable --now podman.socket
184+
```
185+
186+
Verify the socket exists:
187+
188+
```console
189+
$ ls $XDG_RUNTIME_DIR/podman/podman.sock
190+
```
191+
186192
### Verify Subuid and Subgid
187193

188194
Rootless containers require subordinate UID and GID ranges for user namespace mapping. Most distributions configure these automatically when a user account is created. Verify your entries exist:
@@ -338,22 +344,94 @@ Common causes:
338344

339345
### Podman socket not found
340346

341-
If OpenShell reports that no container runtime is available:
347+
If OpenShell reports "Podman is installed but its API socket is not active," the Podman binary is present but the systemd socket unit that exposes its API is not running. This is common on fresh installs where `dnf install podman` or `apt install podman` does not enable the socket automatically.
342348

343-
For rootless mode, verify the socket is running:
349+
For rootful mode, enable the system socket:
344350

345351
```console
346-
$ systemctl --user status podman.socket
347-
$ ls $XDG_RUNTIME_DIR/podman/podman.sock
352+
$ sudo systemctl enable --now podman.socket
348353
```
349354

350-
For rootful mode, verify the system socket:
355+
For rootless mode, enable the user socket:
351356

352357
```console
353-
$ sudo systemctl status podman.socket
354-
$ ls /run/podman/podman.sock
358+
$ systemctl --user enable --now podman.socket
359+
```
360+
361+
If this fails with `$DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined` or `No such file or directory`, see the [systemctl --user fails](#systemctl-user-fails-with-not-defined-or-no-such-file-or-directory) section below.
362+
363+
Verify the socket is active:
364+
365+
```console
366+
$ systemctl --user status podman.socket # rootless
367+
$ sudo systemctl status podman.socket # rootful
368+
```
369+
370+
After enabling the socket, retry `openshell gateway start`.
371+
372+
### systemctl --user fails with "not defined" or "No such file or directory"
373+
374+
`systemctl --user` can fail with two related errors:
375+
376+
**Error: variables not defined**
377+
378+
```
379+
Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined
380+
```
381+
382+
Neither environment variable is set. This happens when you switch users with `su -` or `sudo su`, or when your SSH server does not run PAM session modules.
383+
384+
**Error: No such file or directory**
385+
386+
```
387+
Failed to connect to user scope bus via local transport: No such file or directory
355388
```
356389

390+
The variables are set but the D-Bus bus socket they point to does not exist. There are two common causes:
391+
392+
- **`XDG_RUNTIME_DIR` was empty when `DBUS_SESSION_BUS_ADDRESS` was set.** Because the `DBUS_SESSION_BUS_ADDRESS` export references `$XDG_RUNTIME_DIR`, running `export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus` when `XDG_RUNTIME_DIR` is empty produces `unix:path=/bus`, which does not exist. Check the current value:
393+
394+
```console
395+
$ echo $DBUS_SESSION_BUS_ADDRESS
396+
```
397+
398+
If it shows `unix:path=/bus` (missing the `/run/user/<uid>` prefix), this is the problem.
399+
400+
- **Login lingering is not enabled.** Without lingering, systemd does not start a user session manager, so the bus socket at `/run/user/<uid>/bus` is never created.
401+
402+
**Fix for both errors:**
403+
404+
Run these three steps in order. Each step depends on the one before it:
405+
406+
1. Enable login lingering. This starts the systemd user session manager, which creates the bus socket:
407+
408+
```console
409+
$ sudo loginctl enable-linger $USER
410+
```
411+
412+
2. Set `XDG_RUNTIME_DIR` **first**, then `DBUS_SESSION_BUS_ADDRESS`. The second variable references the first, so the order matters:
413+
414+
```console
415+
$ export XDG_RUNTIME_DIR=/run/user/$(id -u)
416+
$ export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus
417+
```
418+
419+
To make this permanent, add both lines (in this order) to `~/.bashrc` or equivalent.
420+
421+
3. Verify and retry:
422+
423+
```console
424+
$ echo $DBUS_SESSION_BUS_ADDRESS
425+
unix:path=/run/user/1000/bus
426+
$ systemctl --user enable --now podman.socket
427+
```
428+
429+
The `echo` output should show the full path including `/run/user/<uid>`. If it shows `unix:path=/bus`, go back to step 2.
430+
431+
:::{tip}
432+
If you SSH directly as the target user (rather than `su` from root), most distributions set these variables automatically via PAM. If you still see this error after a direct SSH login, check that `UsePAM yes` is set in `/etc/ssh/sshd_config`.
433+
:::
434+
357435
### Permission denied in rootless mode
358436

359437
If you see permission errors when starting containers, verify your subuid/subgid configuration:

0 commit comments

Comments
 (0)