Add checkout bin to sudoers path
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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`,
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user