[BRLTTY] btkey: your Linux console as a Bluetooth keyboard for a phone
Nicolas Pitre
nico at fluxnic.net
Thu Sep 3 23:54:07 UTC 2026
On Thu, 3 Sep 2026, Kyle wrote:
> I already use bluetooth keyboards with my desktop that can pair with up to 2
> other devices,
Sure. What I ultimately wanted is a Bluetooth-capable tenkeyless
mechanical keyboard. I didn't find any.
> I'm also not particularly fond of running something like this as root,
> and I would think this should be able to be avoided by its running
> user being added to the bluetooth group or similar.
On root, no group covers it, because what btkey needs is not "access to
Bluetooth". It needed four things, and it now needs three:
- Grabbing the keyboards is EVIOCGRAB on /dev/input/event*, which is
root:input mode 0660. That one really is a group. It is also larger
than it looks: an evdev node sits below the VT layer, below X and
Wayland, and below any notion of focus, so anything a member of input
runs can read every key on the machine, including a root password
typed at a sudo prompt on another console. Narrower than root in what
it permits, wider in when it applies, since it is permanent and covers
every process you run.
- The HID profile's channels are L2CAP PSM 17 and 19. Below 0x1000, so
binding them wants CAP_NET_BIND_SERVICE, which no group grants. But
btkey need not be the one to bind them: bluetoothd will, for any
client that registers a profile with a PSM, and it does that for an
unprivileged caller. I registered both PSMs as my ordinary uid against
a bluetoothd started without its input plugin, and it bound both.
- Running that bluetoothd, and stopping the system one to make room for
it, is root. This is the expensive choice and it is deliberate: the
alternative removes the Bluetooth HID host role from the machine
permanently, so your own Bluetooth keyboard and mouse stop working.
There is a supported way to do it that way, btkey-system-bluetoothd,
which turns it into a one-time setup.
- Writing the class of device, which only --audio needs, goes through a
raw HCI socket. An ordinary user may read from the controller but not
command it: EPERM without CAP_NET_RAW.
The console was the fourth and is gone as of 0.1.7. btkey now holds its
own /dev/ttyN rather than root's /dev/tty0, and switching consoles from
it runs on the same permission chvt has always had.
On attaching capabilities: I looked, and it is the wrong tool twice
over. File capabilities do not apply to scripts, and btkey's launcher is
Python, so setcap on it is inert exactly as setuid on a script is;
setcap on /usr/bin/python3 would hand the capability to every Python
program on the machine. And item by item they do not fit anyway. The
only capability that opens a root:input node for a non-member is
CAP_DAC_OVERRIDE, which bypasses every file permission check there is.
/var/lib/bluetooth is drwx------ root root, so the pairing keys need
root or that same capability. And stopping a system unit is polkit
rather than a capability: the rule here is auth_admin_keep, a password
prompt, which no capability changes.
What is reachable is your underlying point, through configuration rather
than setcap: usermod -aG input, the one-time bluetoothd setup, the PSMs
taken from bluetoothd instead of bound directly, and no --audio. After
that btkey runs as you, with no sudo, ever. It needs code that does not
exist yet for the PSM part.
Whether that is an improvement is a real question. It trades one
deliberate sudo btkey, gone when the program exits, for two permanent
changes to the machine: the HID host role removed for good, and an
account whose every process can read every keystroke. I would keep sudo
btkey as the default and document the other path. All four items are
written up in docs/DESIGN.md under "Why it runs as root" if you want the
detail.
Nicolas
More information about the BRLTTY
mailing list