Files
omarchycn/test/shell.d/bluetooth-migration-test.sh
c53190be07 Remember Bluetooth on/off through the rfkill soft block (#6682)
* Turn Bluetooth off with an rfkill soft block

BlueZ never persists an adapter's Powered property, so turning Bluetooth off in
the panel lasted only until the next boot. Omarchy's answer was AutoEnable=false,
which persists nothing either — it just means "never power the adapter on", so
Bluetooth came up off every boot whatever the user had chosen.

The soft block already does the job. systemd-rfkill saves every switch under
/var/lib/systemd/rfkill and restores it early on the next boot; that is the
entire purpose of the unit. Blocking also covers every controller at once, where
bluetoothctl only ever addresses the default one.

So the block becomes the state and BlueZ follows it: with AutoEnable back at its
stock default, lifting the block is enough for bluetoothd to power the adapter up
on its own. Powered still tracks the block, so the panel switch and icon read it
exactly as before. Everything that turns Bluetooth on or off goes through
omarchy-bluetooth-power, because bluetoothctl power on fails while a block is set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Carry installed machines over to the rfkill block

Existing installs have AutoEnable=false, so their adapter is down at every boot
and Powered is the only record of what the user actually wants. Read it before
anything changes, hand it to the block, then put AutoEnable back to its default
so bluetoothd can act on that block. Only the exact line Omarchy wrote is
reverted, so a hand-edited opt-out survives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Ask the power helper for a direction, not a toggle

The helper runs detached and the switch only moves once BlueZ catches up, so a
second click inside that window re-read the pre-click state and undid the first.
The panel already knows which way it wants to go, so let it say.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Read every controller and bound the power-up wait

The block hits every Bluetooth radio at once, but the state was read from a bare
bluetoothctl show, which reports the default controller only. A powered dongle
sitting behind a powered-down internal controller read as off and got blocked
along with it. Enumerate the controllers and take any powered one as on, exposed
as is-on so callers do not each reinvent the read.

The wait counted probes rather than time, so a wedged D-Bus turned a two-second
bound into roughly fifty across a full power-up. One deadline around the whole
wait holds it near nine.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Change the radio through sudo in the migration

/dev/rfkill is only writable unelevated from an active graphical seat, so an
update run over SSH failed here with EACCES. Migrations run under bash -e, so
that aborted before the config revert and the marker, and aborted again on every
retry. The privilege guidance already calls for sudo on machine-wide work run
from a visible terminal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 20:27:40 +02:00

110 lines
3.4 KiB
Bash

#!/bin/bash
set -euo pipefail
source "$(dirname "$0")/base-test.sh"
migration="$ROOT/migrations/1786380259.sh"
test_dir=$(mktemp -d)
trap 'rm -rf "$test_dir"' EXIT
mkdir -p "$test_dir/bin"
# sudo runs the real command, so sed acts on the redirected main.conf below and
# the elevated power calls land in the stub beside it.
cat >"$test_dir/bin/sudo" <<'STUB'
#!/bin/bash
printf 'sudo %s\n' "$*" >>"$CALLS"
exec "$@"
STUB
cat >"$test_dir/bin/omarchy-bluetooth-power" <<'STUB'
#!/bin/bash
printf 'omarchy-bluetooth-power %s\n' "$*" >>"$CALLS"
[[ $1 == "is-on" ]] || exit 0
[[ ${POWERED:-} == "yes" ]]
STUB
chmod +x "$test_dir/bin/"*
export CALLS="$test_dir/calls"
marker="$test_dir/marker"
main_conf="$test_dir/main.conf"
reset_machine() {
rm -f "$marker"
printf '[Policy]\nAutoEnable=false\n' >"$main_conf"
}
run_migration() {
: >"$CALLS"
OMARCHY_BLUETOOTH_MIGRATION_MARKER="$marker" \
OMARCHY_BLUETOOTH_MAIN_CONF="$main_conf" \
PATH="$test_dir/bin:$PATH" \
bash -euo pipefail "$migration" >/dev/null
}
# An adapter that is powered right now is one the user turned on, so it stays on.
reset_machine
POWERED=yes run_migration
grep -qx 'omarchy-bluetooth-power on' "$CALLS" ||
fail "migration keeps a powered adapter on" "$(cat "$CALLS")"
pass "migration keeps a powered adapter on"
grep -qx '#AutoEnable=true' "$main_conf" ||
fail "migration puts AutoEnable back to its default" "$(cat "$main_conf")"
pass "migration puts AutoEnable back to its default"
[[ -e $marker ]] || fail "migration records the machine as done"
pass "migration records the machine as done"
# Anything else is a machine that has been booting with Bluetooth off, and the
# block is what carries that over now AutoEnable no longer holds the adapter down.
reset_machine
POWERED=no run_migration
grep -qx 'omarchy-bluetooth-power off' "$CALLS" ||
fail "migration carries an unpowered adapter over to the block" "$(cat "$CALLS")"
pass "migration carries an unpowered adapter over to the block"
# No daemon to ask reads the same way: off is what the machine has been doing.
reset_machine
run_migration
grep -qx 'omarchy-bluetooth-power off' "$CALLS" ||
fail "migration blocks when no adapter can be read" "$(cat "$CALLS")"
pass "migration blocks when no adapter can be read"
# /dev/rfkill is only writable unelevated from an active graphical seat, so an
# update run over SSH would abort here and abort again on every retry.
grep -qx 'sudo omarchy-bluetooth-power off' "$CALLS" ||
fail "migration changes the radio through sudo" "$(cat "$CALLS")"
pass "migration changes the radio through sudo"
# A second account must not undo an administrator's later choice, since migration
# completion is recorded per user.
printf '[Policy]\nAutoEnable=false\n' >"$main_conf"
POWERED=yes run_migration
grep -qx 'AutoEnable=false' "$main_conf" ||
fail "migration leaves a later opt-out alone" "$(cat "$main_conf")"
pass "migration leaves a later opt-out alone"
[[ ! -s $CALLS ]] ||
fail "migration touches no radio state on a second run" "$(cat "$CALLS")"
pass "migration touches no radio state on a second run"
# Only the exact line Omarchy wrote is reverted, so a hand-edited opt-out stands.
reset_machine
printf '[Policy]\nAutoEnable = false\n' >"$main_conf"
POWERED=yes run_migration
grep -qx 'AutoEnable = false' "$main_conf" ||
fail "migration keeps a hand-edited AutoEnable" "$(cat "$main_conf")"
pass "migration keeps a hand-edited AutoEnable"