Add checkout bin to sudoers path

This commit is contained in:
Ryan Hughes
2026-08-11 11:04:50 -04:00
parent 5817feb93f
commit 05bb82b34e
6 changed files with 218 additions and 0 deletions
+43
View File
@@ -14,6 +14,15 @@ fi
prompt_reboot=1 prompt_reboot=1
# sudo resolves a bare command name against secure_path, never the caller's
# PATH, so a dev-linked checkout is invisible to `sudo omarchy-*`: a command the
# package does not ship yet fails outright, and one it does ship silently runs
# the packaged copy while every unprivileged call runs the checkout. Prepending
# the checkout's bin keeps root on the code being edited — the same trust the
# link already extends to every system script Omarchy runs out of $OMARCHY_PATH.
sudoers_file="/etc/sudoers.d/omarchy-dev-path"
system_secure_path="/usr/local/sbin:/usr/local/bin:/usr/bin"
if (( $# < 1 || $# > 2 )) || [[ $1 == "-h" || $1 == "--help" ]]; then if (( $# < 1 || $# > 2 )) || [[ $1 == "-h" || $1 == "--help" ]]; then
cat <<USAGE cat <<USAGE
Usage: omarchy dev link <path-to-checkout> [--no-reboot] Usage: omarchy dev link <path-to-checkout> [--no-reboot]
@@ -28,6 +37,10 @@ themes/, applications/, config/. Files installed at fixed system paths
creation, /usr/share/plymouth) are NOT covered — for those, use creation, /usr/share/plymouth) are NOT covered — for those, use
omarchy-dev-pkg-test to build and install the package from the checkout. omarchy-dev-pkg-test to build and install the package from the checkout.
Also writes $sudoers_file so sudo resolves omarchy-*
from the checkout instead of the packaged copies. That part takes effect
immediately, no reboot needed.
Use --no-reboot when another command will handle the reboot prompt. Use --no-reboot when another command will handle the reboot prompt.
USAGE USAGE
exit 0 exit 0
@@ -51,6 +64,16 @@ omarchy_conf_quote() {
printf '"%s"' "$value" printf '"%s"' "$value"
} }
# A double-quoted sudoers string takes a backslash escape for a literal
# backslash or quote, and nothing else — a checkout path with a space in it is
# already covered by the quotes.
sudoers_quote() {
local value="$1"
value=${value//\\/\\\\}
value=${value//\"/\\\"}
printf '"%s"' "$value"
}
target=$(realpath -e "$1" 2>/dev/null) || { target=$(realpath -e "$1" 2>/dev/null) || {
echo "Error: path does not exist: $1" >&2 echo "Error: path does not exist: $1" >&2
exit 1 exit 1
@@ -62,13 +85,33 @@ for required in bin default shell; do
fi fi
done done
# Staged and parsed before anything is installed: a sudoers file sudo refuses to
# read takes every rule after it down with it, including the %wheel grant, and
# the password prompt needed to undo that is on the other side of the breakage.
staged_sudoers=$(mktemp)
trap 'rm -f "$staged_sudoers"' EXIT
{
printf 'Defaults secure_path='
sudoers_quote "$target/bin:$system_secure_path"
printf '\n'
} >"$staged_sudoers"
if ! visudo -cf "$staged_sudoers" >/dev/null; then
echo "Error: refusing to install an invalid $sudoers_file for $target" >&2
exit 1
fi
{ {
printf 'export OMARCHY_PATH=' printf 'export OMARCHY_PATH='
omarchy_conf_quote "$target" omarchy_conf_quote "$target"
printf '\n' printf '\n'
} | sudo tee /etc/omarchy.conf >/dev/null } | sudo tee /etc/omarchy.conf >/dev/null
sudo install -Dm440 -o root -g root "$staged_sudoers" "$sudoers_file"
echo "Pointed Omarchy at $target" echo "Pointed Omarchy at $target"
echo "sudo now resolves omarchy-* from $target/bin"
echo echo
if (( prompt_reboot )) && gum confirm "Reboot now to activate?"; then if (( prompt_reboot )) && gum confirm "Reboot now to activate?"; then
+22
View File
@@ -6,6 +6,7 @@
set -euo pipefail set -euo pipefail
default_target="/usr/share/omarchy" default_target="/usr/share/omarchy"
sudoers_file="/etc/sudoers.d/omarchy-dev-path"
configured="$default_target" configured="$default_target"
conf_present=0 conf_present=0
linked=0 linked=0
@@ -23,9 +24,30 @@ if [[ -f /etc/omarchy.conf ]]; then
fi fi
fi fi
# /etc/sudoers.d is root-only, so report what sudo resolves rather than reading
# the drop-in — and say so plainly instead of guessing when there is no cached
# credential to ask with. A missing entry here is what makes `sudo omarchy-*`
# run the packaged copy of a command the checkout has changed.
sudo_bin_dir() {
local resolved
if ! sudo -n true 2>/dev/null; then
echo "unknown (needs sudo)"
return
fi
resolved=$(sudo -n bash -c 'type -P omarchy-dev-status' 2>/dev/null) || {
echo "not on sudo's PATH"
return
}
dirname "$resolved"
}
if (( linked )); then if (( linked )); then
echo "dev-link: configured" echo "dev-link: configured"
echo " /etc/omarchy.conf -> OMARCHY_PATH=$configured" echo " /etc/omarchy.conf -> OMARCHY_PATH=$configured"
echo " sudo resolves omarchy-* from: $(sudo_bin_dir)"
echo " status: reboot required before all session layers use this checkout" echo " status: reboot required before all session layers use this checkout"
else else
echo "dev-link: inactive" echo "dev-link: inactive"
+10
View File
@@ -13,6 +13,8 @@ fi
prompt_reboot=1 prompt_reboot=1
sudoers_file="/etc/sudoers.d/omarchy-dev-path"
if (( $# > 1 )); then if (( $# > 1 )); then
echo "Usage: omarchy dev unlink [--no-reboot]" >&2 echo "Usage: omarchy dev unlink [--no-reboot]" >&2
exit 1 exit 1
@@ -32,6 +34,9 @@ Writes /etc/omarchy.conf so OMARCHY_PATH resolves to /usr/share/omarchy
after reboot. This intentionally does not rewrite the running Hyprland, after reboot. This intentionally does not rewrite the running Hyprland,
systemd, shell, or app-launcher environment; reboot to make every layer agree. systemd, shell, or app-launcher environment; reboot to make every layer agree.
Removes $sudoers_file, the drop-in that pointed sudo
at the checkout, so sudo goes back to the packaged omarchy-* immediately.
Use --no-reboot when another command will handle the reboot prompt. Use --no-reboot when another command will handle the reboot prompt.
USAGE USAGE
exit 0 exit 0
@@ -46,6 +51,11 @@ default_target="/usr/share/omarchy"
printf 'export OMARCHY_PATH="%s"\n' "$default_target" | sudo tee /etc/omarchy.conf >/dev/null printf 'export OMARCHY_PATH="%s"\n' "$default_target" | sudo tee /etc/omarchy.conf >/dev/null
# omarchy-dev-link prepended the checkout to sudo's secure_path. Drop it in the
# same step that drops the checkout, or sudo keeps running a tree nothing else
# points at — and keeps trusting a user-writable directory for root's commands.
sudo rm -f "$sudoers_file"
echo "Pointed Omarchy at $default_target" echo "Pointed Omarchy at $default_target"
echo echo
+13
View File
@@ -158,6 +158,19 @@ Sourced by every entry point that needs the env set:
Idempotent — safe to source more than once in the same shell. Idempotent — safe to source more than once in the same shell.
`PATH` covers everything the user runs, but not `sudo`, which resolves command
names against `secure_path` from `/etc/sudoers`. So `omarchy-dev-link` also
writes `/etc/sudoers.d/omarchy-dev-path`:
```
Defaults secure_path="<checkout>/bin:/usr/local/sbin:/usr/local/bin:/usr/bin"
```
Without it, `sudo omarchy-*` fails for a command the package has not shipped
yet and silently runs the packaged copy of one it has. The drop-in is validated
with `visudo -c` before install and removed by `omarchy-dev-unlink`; unlike
`/etc/omarchy.conf`, it takes effect without a reboot.
## Runtime finalization (`omarchy-finalize-user`) ## Runtime finalization (`omarchy-finalize-user`)
Runs once per user. It does **not** copy `~/.config/**`, `~/.bashrc`, Runs once per user. It does **not** copy `~/.config/**`, `~/.bashrc`,
+124
View File
@@ -0,0 +1,124 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT
stub_bin="$test_tmp/bin"
log_file="$test_tmp/dev-link.log"
conf_file="$test_tmp/omarchy.conf"
sudoers_file="$test_tmp/omarchy-dev-path"
mkdir -p "$stub_bin" "$test_tmp/home"
cat >"$stub_bin/sudo" <<'SH'
#!/bin/bash
printf 'sudo' >>"$OMARCHY_DEV_LINK_TEST_LOG"
for arg in "$@"; do
printf '\t%s' "$arg" >>"$OMARCHY_DEV_LINK_TEST_LOG"
done
printf '\n' >>"$OMARCHY_DEV_LINK_TEST_LOG"
case "$1" in
tee)
cat >"$OMARCHY_DEV_LINK_TEST_CONF"
;;
install)
# The staged file is the second-to-last argument.
cp "${@: -2:1}" "$OMARCHY_DEV_LINK_TEST_SUDOERS"
;;
esac
SH
chmod +x "$stub_bin/sudo"
cat >"$stub_bin/gum" <<'SH'
#!/bin/bash
printf 'gum' >>"$OMARCHY_DEV_LINK_TEST_LOG"
for arg in "$@"; do
printf '\t%s' "$arg" >>"$OMARCHY_DEV_LINK_TEST_LOG"
done
printf '\n' >>"$OMARCHY_DEV_LINK_TEST_LOG"
SH
chmod +x "$stub_bin/gum"
cat >"$stub_bin/omarchy-system-reboot" <<'SH'
#!/bin/bash
printf 'reboot\n' >>"$OMARCHY_DEV_LINK_TEST_LOG"
SH
chmod +x "$stub_bin/omarchy-system-reboot"
run_link() {
HOME="$test_tmp/home" \
OMARCHY_DEV_LINK_TEST_LOG="$log_file" \
OMARCHY_DEV_LINK_TEST_CONF="$conf_file" \
OMARCHY_DEV_LINK_TEST_SUDOERS="$sudoers_file" \
PATH="$stub_bin:$PATH" \
"$ROOT/bin/omarchy-dev-link" "$@"
}
make_checkout() {
local checkout="$test_tmp/$1"
mkdir -p "$checkout/bin" "$checkout/default" "$checkout/shell"
printf '%s' "$checkout"
}
checkout=$(make_checkout checkout)
: >"$log_file"
: >"$sudoers_file"
run_link "$checkout" --no-reboot >"$test_tmp/link.out"
[[ $(<"$conf_file") == "export OMARCHY_PATH=\"$checkout\"" ]] ||
fail "dev link points OMARCHY_PATH at the checkout" "$(<"$conf_file")"
pass "dev link points OMARCHY_PATH at the checkout"
# sudo reads secure_path, not the caller's PATH, so the checkout has to come
# first there too or `sudo omarchy-*` runs the packaged copy.
[[ $(<"$sudoers_file") == "Defaults secure_path=\"$checkout/bin:/usr/local/sbin:/usr/local/bin:/usr/bin\"" ]] ||
fail "dev link prepends the checkout to sudo's secure_path" "$(<"$sudoers_file")"
pass "dev link prepends the checkout to sudo's secure_path"
grep -Eq $'^sudo\tinstall\t-Dm440\t-o\troot\t-g\troot\t[^\t]+\t/etc/sudoers\\.d/omarchy-dev-path$' "$log_file" ||
fail "dev link installs the drop-in root-owned and read-only" "$(cat "$log_file")"
pass "dev link installs the drop-in root-owned and read-only"
visudo -cf "$sudoers_file" >/dev/null ||
fail "dev link writes a sudoers drop-in sudo can parse" "$(<"$sudoers_file")"
pass "dev link writes a sudoers drop-in sudo can parse"
grep -F "sudo now resolves omarchy-* from $checkout/bin" "$test_tmp/link.out" >/dev/null ||
fail "dev link reports the sudo change" "$(cat "$test_tmp/link.out")"
pass "dev link reports the sudo change"
if grep -Eq '^(gum|reboot)' "$log_file"; then
fail "dev link --no-reboot skips the reboot prompt" "$(cat "$log_file")"
fi
pass "dev link --no-reboot skips the reboot prompt"
# A path sudoers would have to escape, not one the shell alone handles.
quoted_checkout=$(make_checkout 'check "out"')
: >"$log_file"
: >"$sudoers_file"
run_link "$quoted_checkout" --no-reboot >/dev/null
visudo -cf "$sudoers_file" >/dev/null ||
fail "dev link escapes a checkout path for sudoers" "$(<"$sudoers_file")"
pass "dev link escapes a checkout path for sudoers"
: >"$log_file"
if run_link "$test_tmp/missing" --no-reboot >/dev/null 2>"$test_tmp/missing.err"; then
fail "dev link rejects a path that does not exist"
fi
grep -F "Error: path does not exist: $test_tmp/missing" "$test_tmp/missing.err" >/dev/null ||
fail "dev link explains a path that does not exist" "$(cat "$test_tmp/missing.err")"
if grep -q 'sudo' "$log_file"; then
fail "dev link touches nothing when the path does not exist" "$(cat "$log_file")"
fi
pass "dev link rejects a path that does not exist"
+6
View File
@@ -60,6 +60,12 @@ grep -Fx $'sudo\ttee\t/etc/omarchy.conf' "$log_file" >/dev/null ||
fail "dev unlink writes the package path without rebooting" "$(cat "$log_file")" fail "dev unlink writes the package path without rebooting" "$(cat "$log_file")"
[[ $(<"$conf_file") == 'export OMARCHY_PATH="/usr/share/omarchy"' ]] || [[ $(<"$conf_file") == 'export OMARCHY_PATH="/usr/share/omarchy"' ]] ||
fail "dev unlink writes the package path guard" "$(<"$conf_file")" fail "dev unlink writes the package path guard" "$(<"$conf_file")"
# Left behind, it keeps sudo running a checkout nothing else points at.
grep -Fx $'sudo\trm\t-f\t/etc/sudoers.d/omarchy-dev-path' "$log_file" >/dev/null ||
fail "dev unlink drops the sudo secure_path drop-in" "$(cat "$log_file")"
pass "dev unlink drops the sudo secure_path drop-in"
if grep -Eq '^(gum|reboot)' "$log_file"; then if grep -Eq '^(gum|reboot)' "$log_file"; then
fail "dev unlink --no-reboot skips the reboot prompt" "$(cat "$log_file")" fail "dev unlink --no-reboot skips the reboot prompt" "$(cat "$log_file")"
fi fi