Give SSH commands the user-level tool paths (#6632)

* Give SSH commands the user-level tool paths

ssh host cmd runs neither a login nor an interactive shell, so on Arch it
gets the bare sshd PATH and can't find mise-managed tools like the agent
CLIs herdr scans for. Set PATH in the PAM environment (per-user via
@{HOME}), append the user-level dirs in env-bootstrap so login shells and
the uwsm session get them too, and source env-bootstrap before bashrc's
interactive guard for bash variants that read it non-interactively.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Don't let an empty PATH turn into a cwd entry

Appending with a bare "$PATH:" prefix leaves a leading colon when PATH
is unset, which shells treat as the current directory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
David Heinemeier Hansson
2026-08-08 14:58:29 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent ab367b9477
commit b925431025
6 changed files with 58 additions and 6 deletions
+15 -1
View File
@@ -23,5 +23,19 @@ if [ "$OMARCHY_PATH" != /usr/share/omarchy ]; then
*":${OMARCHY_PATH%/}/bin:"*) ;;
*) PATH="${OMARCHY_PATH%/}/bin${PATH:+:$PATH}" ;;
esac
export PATH
fi
# User-level tool paths, appended so system binaries keep precedence. This is
# what lets login shells (ssh, bash -lc) and the uwsm session find mise-managed
# tools without an interactive rc. Keep these directories in sync with the PAM
# PATH line from install/config/ssh-command-path.sh, which covers SSH commands
# that run no shell setup at all.
case ":$PATH:" in
*":$HOME/.local/share/mise/shims:"*) ;;
*) PATH="${PATH:+$PATH:}$HOME/.local/share/mise/shims" ;;
esac
case ":$PATH:" in
*":$HOME/.local/bin:"*) ;;
*) PATH="${PATH:+$PATH:}$HOME/.local/bin" ;;
esac
export PATH
+4 -2
View File
@@ -1,9 +1,11 @@
# If not running interactively, don't do anything (leave this at the top of this file)
# Omarchy environment (OMARCHY_PATH + PATH), needed even for non-interactive shells
[[ -r /usr/share/omarchy/default/bash/env-bootstrap ]] && source /usr/share/omarchy/default/bash/env-bootstrap
# If not running interactively, don't do anything else (leave this above the rc source)
[[ $- != *i* ]] && return
# All the default Omarchy aliases and functions
# (don't mess with these directly, just overwrite them here!)
[[ -r /usr/share/omarchy/default/bash/env-bootstrap ]] && source /usr/share/omarchy/default/bash/env-bootstrap
source "$OMARCHY_PATH/default/bash/rc"
# Add your own exports, aliases, and functions here.
+1
View File
@@ -2,6 +2,7 @@ run_logged "$OMARCHY_INSTALL/config/theme-system.sh"
run_logged "$OMARCHY_INSTALL/config/increase-lockout-limit.sh"
run_logged "$OMARCHY_INSTALL/config/lockscreen-pam.sh"
run_logged "$OMARCHY_INSTALL/config/fix-powerprofilesctl-shebang.sh"
run_logged "$OMARCHY_INSTALL/config/ssh-command-path.sh"
run_logged "$OMARCHY_INSTALL/config/docker.sh"
run_logged "$OMARCHY_INSTALL/config/snapper.sh"
run_logged "$OMARCHY_INSTALL/config/locate.sh"
+12
View File
@@ -0,0 +1,12 @@
# SSH commands (ssh host cmd) run without a login or interactive shell, so on
# Arch the PAM environment is the only place they can inherit PATH from. Add
# the user-level tool paths there so remote tools (herdr, editors, agent CLIs)
# find mise-managed installs. @{HOME} expands per-user from passwd. Keep the
# directories in sync with default/bash/env-bootstrap.
if ! grep -qE '^PATH[[:space:]]' /etc/security/pam_env.conf; then
cat >>/etc/security/pam_env.conf <<'EOF'
# Omarchy: give SSH commands and other non-shell logins the user-level tool paths
PATH DEFAULT=/usr/local/sbin:/usr/local/bin:/usr/bin:@{HOME}/.local/share/mise/shims:@{HOME}/.local/bin
EOF
fi
+13
View File
@@ -0,0 +1,13 @@
echo "Give SSH commands the user-level tool paths via the PAM environment"
# SSH commands (ssh host cmd) run without a login or interactive shell, so the
# PAM environment is the only place they can inherit PATH from. Same line that
# install/config/ssh-command-path.sh writes on fresh installs; skip if PATH is
# already managed there (by us or by the user).
grep -qE '^PATH[[:space:]]' /etc/security/pam_env.conf && exit 0
sudo tee -a /etc/security/pam_env.conf >/dev/null <<'EOF'
# Omarchy: give SSH commands and other non-shell logins the user-level tool paths
PATH DEFAULT=/usr/local/sbin:/usr/local/bin:/usr/bin:@{HOME}/.local/share/mise/shims:@{HOME}/.local/bin
EOF
+13 -3
View File
@@ -10,6 +10,7 @@ run_bootstrap() {
local home="$3"
local path_value="$4"
shell_bin=$(command -v "$shell_bin")
HOME="$home" PATH="$path_value" "$shell_bin" -c '
. "$1"
printf "%s\n%s\n" "$OMARCHY_PATH" "$PATH"
@@ -53,6 +54,9 @@ default_path=${default_result[1]}
[[ ${default_result[0]} == /usr/share/omarchy ]] || fail "env-bootstrap resolves default OMARCHY_PATH" "actual: ${default_result[0]}"
pass "env-bootstrap resolves default OMARCHY_PATH"
assert_path_present "$default_path" "$tmpdir/unrelated/bin" "env-bootstrap preserves PATH entries in default mode"
assert_path_present "$default_path" "$home/.local/share/mise/shims" "env-bootstrap appends mise shims"
assert_path_present "$default_path" "$home/.local/bin" "env-bootstrap appends ~/.local/bin"
assert_path_first "$default_path" "$tmpdir/unrelated/bin" "env-bootstrap appends user-level paths after existing entries"
printf 'export OMARCHY_PATH="%s"\n' "$tmpdir/active" >"$tmpdir/omarchy.conf"
mapfile -t linked_result < <(run_bootstrap bash "$bootstrap" "$home" "$tmpdir/unrelated/bin:/usr/bin")
@@ -63,10 +67,16 @@ pass "env-bootstrap resolves linked OMARCHY_PATH"
assert_path_first "$linked_path" "$tmpdir/active/bin" "env-bootstrap prepends active checkout bin in linked mode"
assert_path_present "$linked_path" "$tmpdir/unrelated/bin" "env-bootstrap preserves unrelated PATH entries in linked mode"
mapfile -t linked_duplicate_result < <(run_bootstrap bash "$bootstrap" "$home" "$tmpdir/active/bin:/usr/bin")
mapfile -t linked_duplicate_result < <(run_bootstrap bash "$bootstrap" "$home" "$tmpdir/active/bin:/usr/bin:$home/.local/share/mise/shims:$home/.local/bin")
linked_duplicate_path=${linked_duplicate_result[1]}
[[ $linked_duplicate_path == "$tmpdir/active/bin:/usr/bin" ]] || fail "env-bootstrap does not duplicate active checkout bin" "actual PATH: $linked_duplicate_path"
pass "env-bootstrap does not duplicate active checkout bin"
[[ $linked_duplicate_path == "$tmpdir/active/bin:/usr/bin:$home/.local/share/mise/shims:$home/.local/bin" ]] || fail "env-bootstrap does not duplicate PATH entries" "actual PATH: $linked_duplicate_path"
pass "env-bootstrap does not duplicate PATH entries"
# An empty PATH must not produce empty entries (a bare ":" means the cwd)
mapfile -t empty_path_result < <(run_bootstrap bash "$bootstrap" "$home" "")
empty_path=${empty_path_result[1]}
[[ $empty_path == "$tmpdir/active/bin:$home/.local/share/mise/shims:$home/.local/bin" ]] || fail "env-bootstrap builds a clean PATH from an empty one" "actual PATH: $empty_path"
pass "env-bootstrap builds a clean PATH from an empty one"
if command -v zsh >/dev/null 2>&1; then
mapfile -t zsh_result < <(run_bootstrap zsh "$bootstrap" "$home" "$tmpdir/unrelated/bin:/usr/bin")