<feed xmlns='http://www.w3.org/2005/Atom'>
<title>archsetup/tests/bluetooth-resume, branch main</title>
<subtitle>Builds a full dev workstation from a bare Arch Linux install.
</subtitle>
<id>https://git.cjennings.net/archsetup/atom?h=main</id>
<link rel='self' href='https://git.cjennings.net/archsetup/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/'/>
<updated>2026-08-21T14:25:23+00:00</updated>
<entry>
<title>feat(bluetooth): restore the radio after a sleep cycle</title>
<updated>2026-08-21T14:25:23+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-08-21T14:25:23+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=d1b5ee1fdc07400e4308db7d346c5a8be0309c7a'/>
<id>urn:sha1:d1b5ee1fdc07400e4308db7d346c5a8be0309c7a</id>
<content type='text'>
Masking systemd-rfkill so TLP can own radios leaves the resume edge with no owner at all. TLP's sleep hook runs `tlp resume`, but its setting is DEVICES_TO_ENABLE_ON_STARTUP, and TLP has no ON_RESUME to pair with it. WiFi survives anyway because NetworkManager unblocks itself. Bluetooth has no equivalent, so it comes back soft-blocked and stays that way.

After a hibernate the controller is wedged as well, which is why unblocking alone is not the fix. I cleared rfkill by hand on velox this morning and scanning still returned nothing. Zero devices, in a room that gave seventeen a minute after the driver was reloaded. bluetoothd had logged "Failed to set mode" and "Failed to add device" at the instant of resume.

So the hook reloads btusb on a hibernate-class wake, then unblocks. That order matters: a freshly loaded btusb can come up blocked and would undo an earlier unblock. A plain suspend only unblocks, since it brings USB back intact and reloading there would tear down a working adapter for nothing.

It acts only where TLP's own config asks for bluetooth, so it re-asserts a declared intent instead of inventing one and a machine that deliberately keeps the radio off keeps it off. It exits zero on every path, because a failing sleep hook logs noise that outlives the cause.

The install sits next to the mask that creates the gap rather than beside the other installs, and creates /etc/systemd/system-sleep first. Arch does not ship it and install_executable is a plain cp, so without the mkdir a fresh machine would warn and end up with no hook.

Ten tests, and I checked they bite: dropping the TLP intent guard fails three, moving the unblock before the reload fails one.

This surfaced because hibernate was switched back on. The gap predates that and would have shown up the first time the laptop slept.
</content>
</entry>
</feed>
