From 40fecd4d382a29678bf2a35ffd8fbed54878466e Mon Sep 17 00:00:00 2001 From: fred Date: Sat, 15 Aug 2026 15:47:30 -0500 Subject: [PATCH] fix(installer): put $PREFIX/bin on PATH instead of warning about it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three duplicated PATH blocks in tools/install.sh only warned, so an unattended install finished with rc=0 and left `mosaic: command not found` — there was no operator to read the advice and act on it. Measured on a greenfield Debian 13 sandbox: `--next --yes` installed @mosaicstack/mosaic@0.0.50-next.2413 successfully and the CLI was still unreachable. Replaces all three copies with one ensure_prefix_on_path helper that appends the export to ~/.profile (~/.zshenv under zsh) and is a no-op when the prefix is already on PATH or already in a shell profile. Not ~/.bashrc: Debian's default .bashrc returns early for non-interactive shells, so a line appended there is unreachable to `bash -lc`, systemd units and agent seats — the consumers that need the CLI. --- tools/install.sh | 67 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/tools/install.sh b/tools/install.sh index 46c54f34..f41040e4 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -309,6 +309,55 @@ require_cmd() { fi } +# Persist $PREFIX/bin on PATH instead of only warning about it. +# +# The warning it replaces was the last step of an otherwise successful install, +# so the installer reported success and left `mosaic: command not found` — an +# unattended install had no operator to read the advice and act on it. +# +# Deliberately NOT ~/.bashrc: Debian's default .bashrc returns early for +# non-interactive shells, so a PATH line appended to the bottom of it is +# unreachable to `bash -lc`, to systemd units, and to every agent seat — the +# exact consumers that need the CLI. ~/.profile is read by login shells and +# Debian's .profile sources .bashrc for interactive ones, so a single line there +# reaches both. For zsh the always-sourced file is .zshenv, not .zshrc. +ensure_prefix_on_path() { + if [[ ":$PATH:" == *":$PREFIX/bin:"* ]]; then + return + fi + + local profile rc_files=("$HOME/.profile" "$HOME/.zshenv" "$HOME/.zshrc" "$HOME/.bashrc") + if [[ -n "${ZSH_VERSION:-}" ]] || [[ "$(basename "${SHELL:-}")" == "zsh" ]]; then + profile="$HOME/.zshenv" + else + profile="$HOME/.profile" + fi + + if grep -qslF "$PREFIX/bin" "${rc_files[@]}" 2>/dev/null; then + warn "$PREFIX/bin is in your shell profile but not in this shell" + dim " Run: export PATH=\"$PREFIX/bin:\$PATH\" (or start a new login shell)" + return + fi + + # Probe writability in a subshell. A redirection failure on a special built-in + # aborts the shell it runs in, so it has to be a child; and the redirection on + # the subshell is what silences the "Permission denied" the shell would + # otherwise print ahead of our own message. + if ! ( : >>"$profile" ) 2>/dev/null; then + warn "$PREFIX/bin is not on your PATH and $profile could not be written" + dim " Add to your shell rc: export PATH=\"$PREFIX/bin:\$PATH\"" + return + fi + + { + echo "" + echo "# Mosaic CLI" + echo "export PATH=\"$PREFIX/bin:\$PATH\"" + } >>"$profile" + ok "Added $PREFIX/bin to PATH in $profile" + dim " Run: export PATH=\"$PREFIX/bin:\$PATH\" (or start a new login shell)" +} + installed_cli_version() { local json json="$(npm ls -g --depth=0 --json --prefix="$PREFIX" 2>/dev/null)" || true @@ -682,11 +731,7 @@ if [[ "$FLAG_CLI" == "true" ]]; then ensure_monorepo install_cli_from_source - # PATH check for npm prefix - if [[ ":$PATH:" != *":$PREFIX/bin:"* ]]; then - warn "$PREFIX/bin is not on your PATH" - dim " Add to your shell rc: export PATH=\"$PREFIX/bin:\$PATH\"" - fi + ensure_prefix_on_path elif is_next_registry_lane; then info "Next mode — trying fast npm @next install from ${REGISTRY}…" if install_next_cli_from_registry; then @@ -699,11 +744,7 @@ if [[ "$FLAG_CLI" == "true" ]]; then export MOSAIC_GATEWAY_SKIP_NPM_INSTALL=1 fi - # PATH check for npm prefix - if [[ ":$PATH:" != *":$PREFIX/bin:"* ]]; then - warn "$PREFIX/bin is not on your PATH" - dim " Add to your shell rc: export PATH=\"$PREFIX/bin:\$PATH\"" - fi + ensure_prefix_on_path else if [[ -z "$LATEST" ]]; then warn "Could not reach registry at $REGISTRY — skipping npm CLI." @@ -721,11 +762,7 @@ if [[ "$FLAG_CLI" == "true" ]]; then ok "CLI is at or ahead of registry ($CURRENT ≥ $LATEST)." fi - # PATH check for npm prefix - if [[ ":$PATH:" != *":$PREFIX/bin:"* ]]; then - warn "$PREFIX/bin is not on your PATH" - dim " Add to your shell rc: export PATH=\"$PREFIX/bin:\$PATH\"" - fi + ensure_prefix_on_path fi fi