Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ flowchart LR
curl -fsSL https://raw.githubusercontent.com/maxjustships/nomnomcli/main/install.sh | sh
```

This creates or updates a user-level `nomnom` command. The installer tries `uv tool install` first,
then pipx, then a non-virtualenv Python 3.11+ user-site install. It verifies the actual executable,
`nomnom --version`, and `nomnom doctor --json` in a sanitized user/system-only environment. For
bootstrap verification, it deliberately uses `$HOME/.config`: inherited agent XDG roots and every
`NOMNOM_*` override are ignored. It never opens the meal database, cache, or aliases.
This creates or updates a user-level `nomnom` command in `$HOME/.local/bin`. The installer tries
`uv tool install` first, then pipx, then a non-virtualenv Python 3.11+ user-site install. Every
installer selection, invocation, and executable lookup runs in a target-user sanitized environment:
inherited agent `UV_TOOL_*`, `PIPX_*`, and XDG tool-location overrides are ignored, and uv/pipx are
explicitly directed to target-home defaults. It verifies the actual executable, `nomnom --version`,
and `nomnom doctor --json` in that sanitized user/system-only environment. Bootstrap verification
uses `$HOME/.config`; every `NOMNOM_*` override is ignored. It never opens the meal database, cache,
or aliases.

For machine-readable agent output:

Expand Down
264 changes: 158 additions & 106 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,133 @@ fail() {
exit 1
}

target_login_path() {
# The invoking PATH is the only reliable statement of which system paths
# belong to this target user. Never add a global tool directory here: an
# agent or CI image may have installed uv/pipx there for itself.
_login_base=$(sanitize_target_login_path "${PATH:-}")
_login_shell=${SHELL:-/bin/sh}
if [ ! -x "$_login_shell" ]; then
printf '%s' "$_login_base"
return
fi
_login_environment=$(/usr/bin/env -i \
HOME="$HOME" \
USER="${USER:-}" \
LOGNAME="${LOGNAME:-${USER:-}}" \
SHELL="$_login_shell" \
PATH="$_login_base" \
"$_login_shell" -lc '/usr/bin/env' 2>/dev/null || true)
_login_path=$(printf '%s\n' "$_login_environment" | awk '
/^PATH=/ { sub(/^PATH=/, ""); path = $0 }
END { printf "%s", path }
')
_login_path=$(restrict_target_login_path "$_login_path" "$_login_base")
if [ -n "$_login_path" ]; then
printf '%s' "$_login_path"
else
printf '%s' "$_login_base"
fi
}

append_path() {
_append_current=$1
_append_dir=$2
if [ -z "$_append_dir" ]; then
printf '%s' "$_append_current"
elif [ -z "$_append_current" ]; then
printf '%s' "$_append_dir"
else
case ":$_append_current:" in
*":$_append_dir:"*) printf '%s' "$_append_current" ;;
*) printf '%s:%s' "$_append_current" "$_append_dir" ;;
esac
fi
}

sanitize_target_login_path() {
_sanitize_source=$1
_sanitize_result=""
_sanitize_old_ifs=$IFS
IFS=:
for _sanitize_dir in $_sanitize_source; do
[ -n "$_sanitize_dir" ] || continue
case "$_sanitize_dir" in
"$HOME/.local/bin"|"$HOME/bin"|\
/bin|/bin/*|/sbin|/sbin/*|/usr/bin|/usr/bin/*|/usr/sbin|/usr/sbin/*|\
/usr/local/bin|/usr/local/bin/*|/opt/homebrew/bin|/opt/homebrew/bin/*|\
/opt/local/bin|/opt/local/bin/*)
_sanitize_result=$(append_path "$_sanitize_result" "$_sanitize_dir")
;;
esac
done
IFS=$_sanitize_old_ifs
printf '%s' "$_sanitize_result"
}

restrict_target_login_path() {
_restrict_login_path=$(sanitize_target_login_path "$1")
_restrict_base_path=$2
_restrict_result=""
_restrict_old_ifs=$IFS
IFS=:
for _restrict_dir in $_restrict_login_path; do
[ -n "$_restrict_dir" ] || continue
case ":$_restrict_base_path:" in
*":$_restrict_dir:"*)
_restrict_result=$(append_path "$_restrict_result" "$_restrict_dir")
continue
;;
esac
# A target login shell may add these conventional per-user tool
# locations. Do not accept system-wide additions injected by /etc/profile.
case "$_restrict_dir" in
"$HOME/.local/bin"|"$HOME/bin")
_restrict_result=$(append_path "$_restrict_result" "$_restrict_dir")
;;
esac
done
IFS=$_restrict_old_ifs
printf '%s' "$_restrict_result"
}

# All installer tool selection, invocation, and discovery use this target-user
# environment. Do not read UV/PIPX/XDG locations from the invoking agent.
TARGET_BIN_DIR="$HOME/.local/bin"
TARGET_UV_TOOL_DIR="$HOME/.local/share/uv/tools"
TARGET_PIPX_HOME="$HOME/.local/share/pipx"
TARGET_LOGIN_PATH=$(target_login_path)
TARGET_COMMAND_PATH=$(append_path "$TARGET_BIN_DIR" "$(sanitize_target_login_path "$TARGET_LOGIN_PATH")")
[ -n "$TARGET_COMMAND_PATH" ] || TARGET_COMMAND_PATH="$TARGET_BIN_DIR:/usr/bin:/bin"

run_target_environment() {
/usr/bin/env -i \
HOME="$HOME" \
USER="${USER:-}" \
LOGNAME="${LOGNAME:-${USER:-}}" \
SHELL="${SHELL:-/bin/sh}" \
PATH="$TARGET_COMMAND_PATH" \
XDG_CONFIG_HOME="$HOME/.config" \
XDG_DATA_HOME="$HOME/.local/share" \
XDG_CACHE_HOME="$HOME/.cache" \
XDG_STATE_HOME="$HOME/.local/state" \
PYTHONUSERBASE="$HOME/.local" \
UV_TOOL_DIR="$TARGET_UV_TOOL_DIR" \
UV_TOOL_BIN_DIR="$TARGET_BIN_DIR" \
PIPX_HOME="$TARGET_PIPX_HOME" \
PIPX_BIN_DIR="$TARGET_BIN_DIR" \
"$@"
}

find_target_command() {
PATH="$TARGET_COMMAND_PATH" command -v "$1" 2>/dev/null || true
}

run_install() {
if [ "$JSON_OUTPUT" -eq 1 ]; then
"$@" >&2
run_target_environment "$@" >&2
else
"$@"
run_target_environment "$@"
fi
}

Expand All @@ -148,57 +270,46 @@ INSTALL_METHOD=""
INSTALL_BIN_DIR=""
INSTALL_FAILURES=""

if command -v uv >/dev/null 2>&1; then
if run_install uv tool install --force "$REPO_URL"; then
UV_COMMAND=$(find_target_command uv)
if [ -n "$UV_COMMAND" ]; then
if run_install "$UV_COMMAND" tool install --force "$REPO_URL"; then
INSTALL_METHOD="uv"
INSTALL_BIN_DIR=$(uv tool dir --bin 2>/dev/null || true)
[ -n "$INSTALL_BIN_DIR" ] || INSTALL_BIN_DIR=${UV_TOOL_BIN_DIR:-"$HOME/.local/bin"}
INSTALL_BIN_DIR=$(run_target_environment "$UV_COMMAND" tool dir --bin 2>/dev/null || true)
if [ "$INSTALL_BIN_DIR" != "$TARGET_BIN_DIR" ]; then
fail \
"tool_location_mismatch" \
"uv reported a tool executable directory outside the target user's default bin." \
"Ensure uv can use $TARGET_BIN_DIR, then rerun the installer."
fi
else
INSTALL_FAILURES="uv"
note "uv tool install failed; trying the next user-level installer."
fi
fi

if [ -z "$INSTALL_METHOD" ] && command -v pipx >/dev/null 2>&1; then
if run_install pipx install --force "$REPO_URL"; then
PIPX_COMMAND=$(find_target_command pipx)
if [ -z "$INSTALL_METHOD" ] && [ -n "$PIPX_COMMAND" ]; then
if run_install "$PIPX_COMMAND" install --force "$REPO_URL"; then
INSTALL_METHOD="pipx"
INSTALL_BIN_DIR=$(pipx environment --value PIPX_BIN_DIR 2>/dev/null || true)
[ -n "$INSTALL_BIN_DIR" ] || INSTALL_BIN_DIR=${PIPX_BIN_DIR:-"$HOME/.local/bin"}
INSTALL_BIN_DIR=$(run_target_environment "$PIPX_COMMAND" environment --value PIPX_BIN_DIR 2>/dev/null || true)
if [ "$INSTALL_BIN_DIR" != "$TARGET_BIN_DIR" ]; then
fail \
"tool_location_mismatch" \
"pipx reported an executable directory outside the target user's default bin." \
"Ensure pipx can use $TARGET_BIN_DIR, then rerun the installer."
fi
else
INSTALL_FAILURES="${INSTALL_FAILURES:+$INSTALL_FAILURES,}pipx"
note "pipx install failed; trying the system-Python user-site fallback."
fi
fi

append_path() {
_append_current=$1
_append_dir=$2
if [ -z "$_append_dir" ]; then
printf '%s' "$_append_current"
elif [ -z "$_append_current" ]; then
printf '%s' "$_append_dir"
else
case ":$_append_current:" in
*":$_append_dir:"*) printf '%s' "$_append_current" ;;
*) printf '%s:%s' "$_append_current" "$_append_dir" ;;
esac
fi
}

python_search_path() {
_python_result=""
_python_old_ifs=$IFS
IFS=:
for _python_dir in ${PATH:-}; do
for _python_dir in $TARGET_COMMAND_PATH; do
[ -n "$_python_dir" ] || continue
if [ -n "${VIRTUAL_ENV:-}" ]; then
case "$_python_dir" in
"$VIRTUAL_ENV"|"$VIRTUAL_ENV"/*) continue ;;
esac
fi
case "$_python_dir" in
*"/.venv"|*"/.venv/"*|*"/venv/"*|*"/.hermes/"*|*"/.codex/"*) continue ;;
esac
_python_result=$(append_path "$_python_result" "$_python_dir")
done
IFS=$_python_old_ifs
Expand All @@ -218,26 +329,19 @@ find_system_python() {
*":$_find_candidate:"*) continue ;;
esac
_find_seen="${_find_seen:+$_find_seen:}$_find_candidate"
if ! /usr/bin/env -u VIRTUAL_ENV -u PYTHONPATH -u PYTHONHOME \
"$_find_candidate" -c '
if ! run_target_environment "$_find_candidate" -c '
import pip # noqa: F401
import sys
base_prefix = getattr(sys, "base_prefix", sys.prefix)
raise SystemExit(0 if sys.version_info >= (3, 11) and sys.prefix == base_prefix else 1)
' >/dev/null 2>&1; then
continue
fi
_find_resolved=$(/usr/bin/env -u VIRTUAL_ENV -u PYTHONPATH -u PYTHONHOME \
"$_find_candidate" -c '
_find_resolved=$(run_target_environment "$_find_candidate" -c '
import os, sys
print(os.path.realpath(sys.executable))
' 2>/dev/null || true)
[ -n "$_find_resolved" ] || _find_resolved=$_find_candidate
if [ -n "${VIRTUAL_ENV:-}" ]; then
case "$_find_resolved" in
"$VIRTUAL_ENV"|"$VIRTUAL_ENV"/*) continue ;;
esac
fi
IFS=$_find_old_ifs
printf '%s' "$_find_candidate"
return 0
Expand All @@ -256,31 +360,29 @@ if [ -z "$INSTALL_METHOD" ]; then
"Install uv or pipx, or install Python 3.11+ in your normal shell PATH, then rerun this installer."
fi
note "No isolated tool installer completed; using the system-Python user-site fallback."
if ! run_install /usr/bin/env \
-u VIRTUAL_ENV -u PYTHONPATH -u PYTHONHOME -u PIP_REQUIRE_VIRTUALENV \
"$SYSTEM_PYTHON" -m pip install --user --upgrade "$REPO_URL"; then
if ! run_install "$SYSTEM_PYTHON" -m pip install --user --upgrade "$REPO_URL"; then
fail \
"installation_failed" \
"The system-Python user-site installation failed${INSTALL_FAILURES:+ after $INSTALL_FAILURES failed}." \
"Review the installer output, ensure Git and network access are available, then rerun."
fi
INSTALL_METHOD="user-site"
INSTALL_BIN_DIR=$(/usr/bin/env -u VIRTUAL_ENV -u PYTHONPATH -u PYTHONHOME \
"$SYSTEM_PYTHON" -c '
INSTALL_BIN_DIR=$(run_target_environment "$SYSTEM_PYTHON" -c '
import sysconfig
print(sysconfig.get_path("scripts", scheme=sysconfig.get_preferred_scheme("user")))
' 2>/dev/null || true)
[ -n "$INSTALL_BIN_DIR" ] || INSTALL_BIN_DIR="$HOME/.local/bin"
if [ "$INSTALL_BIN_DIR" != "$TARGET_BIN_DIR" ]; then
fail \
"tool_location_mismatch" \
"The system-Python user-site fallback reported an executable directory outside the target user's default bin." \
"Ensure Python can use $TARGET_BIN_DIR, then rerun the installer."
fi
fi

CANDIDATE_DIRS=""
for _candidate_dir in \
"$INSTALL_BIN_DIR" \
"${UV_TOOL_BIN_DIR:-}" \
"${PIPX_BIN_DIR:-}" \
"${XDG_BIN_HOME:-}" \
"$HOME/.local/bin" \
"$HOME/bin"; do
"$TARGET_BIN_DIR"; do
[ -n "$_candidate_dir" ] || continue
CANDIDATE_DIRS=$(append_path "$CANDIDATE_DIRS" "$_candidate_dir")
done
Expand All @@ -302,61 +404,11 @@ if [ -z "$EXECUTABLE" ]; then
"Inspect $INSTALL_BIN_DIR and rerun with --status-json for structured diagnostics."
fi

normal_login_path() {
_login_base="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:/opt/local/bin"
_login_shell=${SHELL:-/bin/sh}
if [ ! -x "$_login_shell" ]; then
printf '%s' "$_login_base"
return
fi
_login_environment=$(/usr/bin/env -i \
HOME="$HOME" \
USER="${USER:-}" \
LOGNAME="${LOGNAME:-${USER:-}}" \
SHELL="$_login_shell" \
PATH="$_login_base" \
"$_login_shell" -lc '/usr/bin/env' 2>/dev/null || true)
_login_path=$(printf '%s\n' "$_login_environment" | awk '
/^PATH=/ { sub(/^PATH=/, ""); path = $0 }
END { printf "%s", path }
')
if [ -n "$_login_path" ]; then
printf '%s' "$_login_path"
else
printf '%s' "$_login_base"
fi
}

sanitize_normal_path() {
_sanitize_source=$1
_sanitize_result=""
_sanitize_old_ifs=$IFS
IFS=:
for _sanitize_dir in $_sanitize_source; do
[ -n "$_sanitize_dir" ] || continue
if [ -n "${VIRTUAL_ENV:-}" ]; then
case "$_sanitize_dir" in
"$VIRTUAL_ENV"|"$VIRTUAL_ENV"/*) continue ;;
esac
fi
case "$_sanitize_dir" in
*"/.venv"|*"/.venv/"*|*"/venv/"*|*"/.hermes/"*|*"/.codex/"*) continue ;;
esac
case "$_sanitize_dir" in
"$INSTALL_BIN_DIR"|"${UV_TOOL_BIN_DIR:-__unset__}"|"${PIPX_BIN_DIR:-__unset__}"|\
"$HOME/.local/bin"|"$HOME/bin"|"${XDG_BIN_HOME:-__unset__}"|\
/bin|/bin/*|/sbin|/sbin/*|/usr/bin|/usr/bin/*|/usr/sbin|/usr/sbin/*|\
/usr/local/bin|/usr/local/bin/*|/opt/homebrew/bin|/opt/homebrew/bin/*|\
/opt/local/bin|/opt/local/bin/*)
_sanitize_result=$(append_path "$_sanitize_result" "$_sanitize_dir")
;;
esac
done
IFS=$_sanitize_old_ifs
printf '%s' "$_sanitize_result"
sanitize_target_login_path "$1"
}

LOGIN_PATH=$(normal_login_path)
LOGIN_PATH=$TARGET_LOGIN_PATH
SANITIZED_LOGIN_PATH=$(sanitize_normal_path "$LOGIN_PATH")
VERIFY_PATH=$(append_path "$INSTALL_BIN_DIR" "$SANITIZED_LOGIN_PATH")

Expand Down
Loading
Loading