Repository: systemd/mkosi

Issue:
Missing group prevents dbus installation
I often install `dbus` into an image so when a `systemd-nspawn` container is booted from it I can get a shell and test things out. When using the main branch to track down another problem, I found that this was causing build failures.

Using a simple configuration

```ini
[Distribution]
Distribution=debian
Release=bookworm

[Output]
CacheDirectory=cache
ImageId=test

[Content]
Packages=
  dbus
  systemd
BuildPackages=
  make
BuildScript=build.sh
```

and the trivial build script

```bash
#!/usr/bin/bash
echo "building"
make --version
```

Attempting to build this with `mkosi` succeeds in the basic installation of Debian and then the common packages, but when it tries to install the build packages it fails with

```console
dpkg: unrecoverable fatal error, aborting:
 unknown system group 'messagebus' in statoverride file; the system group got removed
before the override, which is most probably a packaging bug, to recover you
can remove the override manually with dpkg-statoverride
E: Sub-process /usr/bin/dpkg returned an error code (2)
```

I am not a Debian expert, but from searching I found that this comes about due to a line in `/var/lib/dpkg/statoverride`. A bit more digging and I found the line was `root messagebus 4754 /usr/lib/dbus-1.0/dbus-daemon-launch-helper`, which tries to set the properties of the given file. The error message suggests that the `messagebus` group is not available in the build image. The `messagebus` system user and it group is created by the postinst script for dbus-system-bus-common (a dependency of dbus).

To further debug, I modified `mkosi/distributions/debian.py` and added the following code to the start of the `install_packages` method (L126 in current main branch):

```python
        ovr = state.root / "var/lib/dpkg/statoverride"
        if ovr.exists():
            run(["grep", "messagebus", state.root / "etc/passwd"])
            run(["grep", "messagebus", state.root / "etc/group"])
```

When this is run, the output is

```console
‣  Installing build packages for Debian
messagebus:x:100:101::/nonexistent:/usr/sbin/nologin
messagebus:x:101:
```

which suggested that the user and group are present in those files. I then modified the debug code to get dpkg to remove the offending override:

```python
        ovr = state.root / "var/lib/dpkg/statoverride"
        if ovr.exists():
            run(["dpkg-statoverride", "--root", state.root, "--remove", "/usr/lib/dbus-1.0/dbus-daemon-launch-helper"])
```

With this, the image built successfully. The line is present in the final image -- removing this from the build overlay does not effect the final image.

Having not encountered this problem before, I went back to 0af3f8a (v15.1 release) and found that the image built with no local modifications to the source. I then started a Git bisect session:

```console
$ git bisect start
status: waiting for both good and bad commits
$ git bisect bad 7030d76
status: waiting for good commit(s), bad commit known
$ git bisect good 0af3f8a
Bisecting: 35 revisions left to test after this (roughly 5 steps)
[fbabc572b19a0a86c48d6901e0008c439c4ddfcf] Add none Bootloader option
$ <testing each bisect commit by attempting to build the image>
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[ca9673cbcbd9f293e5566cec4a1ba14bbcd075b8] skip passwd bind mounts if the path does not exist on the host
```

The last working commit was 9ae416d with ca9673c (from #1793) causing the failure (to make sure, I confirmed these two outside of the bisect session). I guess this also explains the inconsistent result in the earlier debugging -- the user and group were present in the files under `state.root` but are presumably being masked from dpkg by the change in the bind mounts introduced by this commit.


Discussion:

Resolving patch:
diff --git a/mkosi/run.py b/mkosi/run.py
--- a/mkosi/run.py
+++ b/mkosi/run.py
@@ -355,7 +355,7 @@ def finalize_passwd_mounts(root: Path) -> list[PathString]:
     options: list[PathString] = []
 
     for f in ("passwd", "group", "shadow", "gshadow"):
-        if not (Path("etc") / f).exists():
+        if not (Path("/etc") / f).exists():
             continue
         p = root / "etc" / f
         if p.exists():


Test patch: