Flag a reboot when the docker group changes (#8080)

Group membership is fixed at login, so removing (or adding) the docker group
does not take effect in the running session. The existing-user migration and the
Setup > Security toggles now call `omarchy-state set reboot-required`, so
omarchy-update-restart prompts for the reboot that actually applies the change
(and the bar shows it pending). A plain log out and back in still works.

The migration test now exercises the real removal command and omarchy-state
rather than a stub, asserting the reboot flag is set on removal and left alone
when the user is already out of the group.


Claude-Session: https://claude.ai/code/session_01Gb7x6poap4hGCndPx5qt5T

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Omarchybot
2026-08-24 19:32:37 +02:00
committed by GitHub
co-authored by David Heinemeier Hansson Claude Opus 4.8
parent b5ded31e2f
commit 1565919c87
3 changed files with 48 additions and 30 deletions
+6 -1
View File
@@ -13,8 +13,13 @@ fi
echo "Removing $USER from the docker group..." echo "Removing $USER from the docker group..."
sudo gpasswd -d "$USER" docker >/dev/null sudo gpasswd -d "$USER" docker >/dev/null
# Group membership is fixed at login, so the running session keeps its docker
# access until it ends. Flag a reboot so omarchy-update-restart prompts for one
# (and the bar shows it pending); a plain log out and back in works too.
omarchy-state set reboot-required
echo "" echo ""
echo "Sudoless Docker DISABLED. Log out and back in for the change to take effect." echo "Sudoless Docker DISABLED. Reboot (or log out and back in) for the change to take effect."
echo "Docker access now goes through a polkit/sudo prompt again: the Docker TUI" echo "Docker access now goes through a polkit/sudo prompt again: the Docker TUI"
echo "(Super + Shift + D) and the Windows VM will ask when they need it, and the" echo "(Super + Shift + D) and the Windows VM will ask when they need it, and the"
echo "plain 'docker' CLI runs under sudo." echo "plain 'docker' CLI runs under sudo."
+5 -1
View File
@@ -28,8 +28,12 @@ echo ""
if gum confirm "Enable sudoless Docker? This gives anything running as you passwordless root."; then if gum confirm "Enable sudoless Docker? This gives anything running as you passwordless root."; then
sudo usermod -aG docker "$USER" sudo usermod -aG docker "$USER"
# Group membership is fixed at login, so docker won't be reachable without a
# prompt until the session restarts. Flag a reboot so omarchy-update-restart
# prompts for one (and the bar shows it pending).
omarchy-state set reboot-required
echo "" echo ""
echo "Sudoless Docker ENABLED. Log out and back in (or run 'newgrp docker')" echo "Sudoless Docker ENABLED. Reboot, or log out and back in (or run 'newgrp docker'),"
echo "for the new group membership to take effect." echo "for the new group membership to take effect."
echo "To disable it again, run: omarchy-remove-security-sudoless-docker" echo "To disable it again, run: omarchy-remove-security-sudoless-docker"
else else
+37 -28
View File
@@ -1,8 +1,13 @@
#!/bin/bash #!/bin/bash
# #
# The docker-group opt-in migration must remove an existing install's user from # The docker-group opt-in migration must remove an existing install's user from
# the root-equivalent docker group (only when they are in it), refresh the stale # the root-equivalent docker group (only when they are in it), flag a reboot so
# Docker launcher entry, and stay idempotent on reruns. # the group change actually takes effect (group membership is fixed at login),
# refresh the stale Docker launcher entry, and stay idempotent on reruns.
#
# The real omarchy-remove-security-sudoless-docker and omarchy-state run here
# (from the repo bin); only the privileged/system calls are stubbed, so the whole
# chain — including the reboot flag — is exercised.
set -euo pipefail set -euo pipefail
@@ -17,49 +22,53 @@ omarchy_path="$test_dir/omarchy"
stub_bin="$test_dir/bin" stub_bin="$test_dir/bin"
mkdir -p "$home/.local/share/applications" "$omarchy_path/applications" "$stub_bin" mkdir -p "$home/.local/share/applications" "$omarchy_path/applications" "$stub_bin"
# The packaged (new) launcher entry the migration should copy over the stale one.
printf 'NEW-LAUNCHER\n' >"$omarchy_path/applications/Docker.desktop" printf 'NEW-LAUNCHER\n' >"$omarchy_path/applications/Docker.desktop"
printf 'OLD-LAUNCHER\n' >"$home/.local/share/applications/Docker.desktop" printf 'OLD-LAUNCHER\n' >"$home/.local/share/applications/Docker.desktop"
# Stub id to report a controllable group set, and the removal command to record # id reports a controllable group set; sudo just drops the prefix; gpasswd
# that it was called instead of touching the real system. # records its call instead of touching the real system.
cat >"$stub_bin/id" <<'STUB' cat >"$stub_bin/id" <<'STUB'
#!/bin/bash #!/bin/bash
# Only the migration's `id -nG "$USER"` needs answering here.
printf '%s\n' "${STUB_GROUPS:-wheel input}" printf '%s\n' "${STUB_GROUPS:-wheel input}"
STUB STUB
cat >"$stub_bin/omarchy-remove-security-sudoless-docker" <<'STUB' cat >"$stub_bin/sudo" <<'STUB'
#!/bin/bash #!/bin/bash
touch "${REMOVE_CALLED:?}" exec "$@"
STUB STUB
chmod +x "$stub_bin/id" "$stub_bin/omarchy-remove-security-sudoless-docker" cat >"$stub_bin/gpasswd" <<'STUB'
#!/bin/bash
echo "$@" >>"${GPASSWD_CALLS:?}"
STUB
chmod +x "$stub_bin/id" "$stub_bin/sudo" "$stub_bin/gpasswd"
reboot_flag="$home/.local/state/omarchy/reboot-required"
gpasswd_calls="$test_dir/gpasswd-calls"
launcher="$home/.local/share/applications/Docker.desktop"
remove_called="$test_dir/remove-called"
run_migration() { run_migration() {
rm -f "$remove_called" rm -f "$gpasswd_calls" "$reboot_flag"
HOME="$home" OMARCHY_PATH="$omarchy_path" USER="tester" \ HOME="$home" OMARCHY_PATH="$omarchy_path" USER="tester" STUB_GROUPS="$1" \
STUB_GROUPS="$1" REMOVE_CALLED="$remove_called" \ GPASSWD_CALLS="$gpasswd_calls" PATH="$stub_bin:$ROOT/bin:$PATH" \
PATH="$stub_bin:$PATH" bash -euo pipefail "$migration" >/dev/null 2>&1 bash -euo pipefail "$migration" >/dev/null 2>&1
} }
# In the docker group: the user is removed and the launcher is refreshed. # In the docker group: user removed, reboot flagged, launcher refreshed.
run_migration "wheel input docker" || fail "migration runs when the user is in the docker group" run_migration "wheel input docker" || fail "migration runs when the user is in the docker group"
[[ -e $remove_called ]] || fail "migration removes a user who is in the docker group" grep -q -- "-d tester docker" "$gpasswd_calls" || fail "migration removes the user from the docker group"
[[ $(cat "$home/.local/share/applications/Docker.desktop") == "NEW-LAUNCHER" ]] || [[ -f $reboot_flag ]] || fail "migration flags a reboot so the group change takes effect"
fail "migration refreshes the stale Docker launcher entry" [[ $(cat "$launcher") == "NEW-LAUNCHER" ]] || fail "migration refreshes the stale Docker launcher entry"
pass "migration removes the docker group and refreshes the launcher" pass "migration removes the group, flags a reboot, and refreshes the launcher"
# Not in the docker group (fresh install, or already migrated): no removal. # Not in the docker group (fresh install, or already migrated): nothing changes.
printf 'OLD-LAUNCHER\n' >"$home/.local/share/applications/Docker.desktop" printf 'OLD-LAUNCHER\n' >"$launcher"
run_migration "wheel input" || fail "migration runs when the user is not in the docker group" run_migration "wheel input" || fail "migration runs when the user is not in the docker group"
[[ ! -e $remove_called ]] || fail "migration must not call the removal when the user is not in the docker group" [[ ! -f $gpasswd_calls ]] || fail "migration must not touch the group when the user is not in it"
[[ $(cat "$home/.local/share/applications/Docker.desktop") == "NEW-LAUNCHER" ]] || [[ ! -f $reboot_flag ]] || fail "migration must not flag a reboot when nothing changed"
fail "migration still refreshes the launcher when the group is already absent" [[ $(cat "$launcher") == "NEW-LAUNCHER" ]] || fail "migration still refreshes the launcher when the group is already absent"
pass "migration is a no-op on the group when it is already absent" pass "migration is a no-op on the group and reboot when already out"
# No launcher entry present: the refresh is skipped without error. # No launcher entry present: the refresh is skipped without error.
rm -f "$home/.local/share/applications/Docker.desktop" rm -f "$launcher"
run_migration "wheel input" || fail "migration tolerates a missing launcher entry" run_migration "wheel input" || fail "migration tolerates a missing launcher entry"
[[ ! -e $home/.local/share/applications/Docker.desktop ]] || [[ ! -e $launcher ]] || fail "migration does not create a launcher entry that was not there"
fail "migration does not create a launcher entry that was not there"
pass "migration skips the launcher refresh when no entry exists" pass "migration skips the launcher refresh when no entry exists"