bluez: new sysext shipping the BlueZ Bluetooth userspace - #253
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new bluez system extension to ship the BlueZ Bluetooth userspace (daemon + CLI tools) on Flatcar via the sysext-bakery, along with the needed systemd integration and documentation/version metadata.
Changes:
- Introduces
bluez.sysext/create.shto build BlueZ from Debian (stable/testing) and bundle the full shared-library closure. - Adds system integration files (systemd unit + start-on-merge drop-in, tmpfiles entry) so
bluetoothdcan run and persist state. - Registers the extension in version metadata and documentation, including a dedicated
docs/bluez.mdpage.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| release_build_versions.txt | Registers bluez versions (5.82, latest) for the bakery build matrix. |
| docs/index.md | Adds bluez to the published extensions index. |
| docs/bluez.md | New user documentation and Butane provisioning snippet for the BlueZ sysext. |
| bluez.sysext/test.sh | Adds the extension test entry point (currently empty). |
| bluez.sysext/files/usr/lib/tmpfiles.d/10-bluez.conf | Creates /var/lib/bluetooth for adapter state and pairing keys. |
| bluez.sysext/files/usr/lib/systemd/system/multi-user.target.d/10-bluez.conf | Ensures bluetooth.service is upheld by multi-user.target (start-on-merge behavior). |
| bluez.sysext/files/usr/lib/systemd/system/bluetooth.service | Defines the bluetoothd systemd unit (D-Bus service, guarded by sysfs presence). |
| bluez.sysext/create.sh | Implements the Debian-based build, suite/version selection, and closure bundling via tools/flix.sh. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
bluez.sysext/files/usr/lib/systemd/system/bluetooth.service:21
LimitNPROC=1is very likely too restrictive forbluetoothd(threads count toward RLIMIT_NPROC on Linux), and it also deviates from the repo’s established convention of setting this toinfinityfor long-running daemons (e.g.docker.sysext/files/usr/lib/systemd/system/docker.service:15,containerd.sysext/.../containerd.service:11). This could cause startup/runtime failures under GLib/D-Bus as soon as an additional thread/task is created.
LimitNPROC=1
Adds a bluez extension providing bluetoothd plus bluetoothctl, btmon, btmgmt and btattach, so Bluetooth can be used on Flatcar without baking BlueZ into the OS image. BlueZ has no upstream static or portable release and links glib, dbus and readline, so the binaries are bundled with their library closure (glibc and the dynamic loader included) from a Debian container and handed to tools/flix.sh, which patchelf's them onto a private loader/rpath. This is the same approach as qemu.sysext and tilde.sysext. Debian packages BlueZ, so the version parameter selects the suite (stable/testing) shipping it. The extension is userspace only. Bluetooth kernel modules must match the running kernel exactly, so they belong in the image; flatcar/scripts#4197 enables them. bluetooth.service is guarded by ConditionPathIsDirectory on /sys/class/bluetooth, so on an image without those modules the unit is skipped rather than failed, and the Upholds= drop-in does not retry it in a loop. The unit reloads D-Bus configuration before starting bluetoothd: the extension drops a new policy file into /usr/share/dbus-1/system.d, which a running dbus-daemon has not read yet, and without the reload bluetoothd cannot take the org.bluez name until the next reboot. Built and checked for both architectures against Debian stable (BlueZ 5.82): the bundled bluetoothd, bluetoothctl and btmon all run and report their version on an Alpine (musl, non-Debian) host, confirming the closure is self-contained. Signed-off-by: Ananth Bhaskararaman <antsub@gmail.com>
Address review feedback: - bluetooth.service used WantedBy=bluetooth.target, following upstream BlueZ. systemd does ship that target, but nothing pulls it in by default, so 'systemctl enable bluetooth.service' would leave the service unstarted. Every other service unit in this repo uses multi-user.target; match that. - The docs pointed the sysupdate config at .../extensions/bluez/bluez.conf. That nested form does resolve (chrony and bird use it), but the flat .../extensions/<name>.conf form is what the other 28 docs use. Signed-off-by: Ananth Bhaskararaman <antsub@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.
Suppressed comments (2)
bluez.sysext/create.sh:170
- Only the policy file is copied here. Debian's
bluezpackage also provides the D-Bus activation descriptor fororg.bluez; without that descriptor (and a livedbus-org.bluez.servicealias, since the[Install] Alias=entry is not itself a symlink), D-Bus cannot activate BlueZ when the unit is not already running. Include the activation descriptor and alias, or provide an equivalent explicit activation path.
for c in /usr/share/dbus-1/system.d/bluetooth.conf \
/etc/dbus-1/system.d/bluetooth.conf; do
[ -f "${c}" ] && dbus_conf="${c}" && break
bluez.sysext/create.sh:164
bluetoothdloads BlueZ plugin modules from the Debian package's.../bluetooth/plugins/directory at runtime, butflix.shonly copies the explicitly listed executables and their DT_NEEDED libraries. None of those plugin.sofiles are included, so the daemon can start while profiles/policy functionality provided by the packaged plugins is missing. Include the plugin modules and their library closure in the private layout (and configure the daemon to find that layout).
paths="${bluetoothd}"
for b in bluetoothctl btmon btmgmt btattach bluemoon hciconfig hcitool \
sdptool l2ping rctest; do
[ -x "/usr/bin/${b}" ] && paths="${paths} /usr/bin/${b}"
done
The unit was derived from upstream BlueZ's src/bluetooth.service.in but dropped its whole hardening block, keeping only LimitNPROC=1. Restore it: CapabilityBoundingSet, NoNewPrivileges, ProtectHome, ProtectSystem=strict, PrivateTmp, ProtectKernelTunables, ProtectControlGroups, MemoryDenyWriteExecute and RestrictRealtime. StateDirectory=bluetooth replaces the tmpfiles.d rule, which ProtectSystem=strict would otherwise render useless: the daemon needs /var/lib/bluetooth declared to keep it writable. ConditionPathIsDirectory=/sys/class/bluetooth is only evaluated when the unit is queued, so a controller appearing later -- a hot-plugged dongle, or btusb loading after multi-user.target -- left bluetoothd stopped for good. Ship a udev rule that pulls the unit in from the device event. Finally, take the CLI tools from the bluez package instead of a hardcoded probe list, which silently dropped tools Debian still ships: bccmd, ciptool, hex2hcd, rfcomm, and hciattach, the one that attaches a UART controller. Also add the repo's usual curl retry flags to the Debian API lookups. Signed-off-by: Ananth Bhaskararaman <antsub@gmail.com>
| # Adapter settings and pairing keys. Replaces a tmpfiles.d rule: systemd creates | ||
| # the directory before the daemon starts, and ProtectSystem=strict below needs | ||
| # it declared here to leave it writable. | ||
| StateDirectory=bluetooth | ||
| StateDirectoryMode=0700 |
Adds a
bluezsysext shipping the BlueZ Bluetooth userspace:bluetoothdplusbluetoothctl,btmon,btmgmt,btattachand the legacy tools that are still packaged.This is the userspace half of adding Bluetooth support to Flatcar. The kernel half is flatcar/scripts#4197, which enables the Bluetooth modules in the image.
How it's built
BlueZ has no upstream static or portable release and links glib, dbus and readline, so the binaries are bundled with their full library closure (glibc and the dynamic loader included) from a Debian container and handed to
tools/flix.sh, which patchelf's them onto a private loader/rpath. Same approach asqemu.sysextandtilde.sysext.The Debian layout is probed rather than hardcoded:
bluetoothdmoved from/usr/libto/usr/libexecacross releases, and the deprecated tools (hciconfig,hcitool, …) are absent from newer ones.Behaviour on merge
bluetooth.serviceis upheld bymulti-user.targetand runsbluetoothdas a D-Bus service underorg.bluez.The unit is guarded by
ConditionPathIsDirectory=/sys/class/bluetooth. On an image without the Bluetooth modules the unit is skipped rather than failed. On an image that has them, the controller's driver autoloads on detection, the directory appears, and the daemon starts./var/lib/bluetooth(adapter settings and pairing keys) is created by a shipped tmpfiles snippet.Testing
Built for both architectures against Debian stable (BlueZ 5.82).
For each, I extracted the image and ran the bundled binaries on an Alpine (musl, non-Debian) host with only
/usr/local/bluezbind-mounted —bluetoothd,bluetoothctlandbtmonall start and report5.82, which confirms the library closure is genuinely self-contained and not falling back to host libraries.I also checked that the image contains no
/usr/sbin(per the warning in_skel.sysext/create.sh) and that the D-Bus policy, units and tmpfiles snippet land where they should.