Files
stack/packages/mosaic/framework/tools/fleet/pane-runtime-path.sh
T

200 lines
6.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Canonical fleet-pane PATH construction and executable reachability checks.
#
# This file is both sourceable by start-agent-session.sh and executable by the
# fleet CLI preflight. Keep the pane PATH in one implementation: provisioning
# checks and the eventual pane must answer the same question.
mosaic_fleet_pane_home() {
local mosaic_home="$1"
local fallback_home="$2"
case "$mosaic_home" in
*/.config/mosaic) printf '%s' "${mosaic_home%/.config/mosaic}" ;;
*) printf '%s' "$fallback_home" ;;
esac
}
mosaic_fleet_build_runtime_bin_prefix() {
local pane_home="$1"
local runtime_bin="${2:-}"
local candidates=()
if [ -n "$runtime_bin" ]; then candidates+=("$runtime_bin"); fi
if command -v npm >/dev/null 2>&1; then
local npm_prefix
npm_prefix=$(npm config get prefix 2>/dev/null) || true
if [ -n "$npm_prefix" ]; then candidates+=("${npm_prefix}/bin"); fi
fi
candidates+=("$pane_home/.npm-global/bin" "$pane_home/.local/bin")
local prefix="" dir
for dir in "${candidates[@]}"; do
[ -d "$dir" ] || continue
case ":${prefix}:" in *":${dir}:"*) ;; *) prefix="${prefix:+$prefix:}$dir" ;; esac
done
printf '%s' "$prefix"
}
mosaic_fleet_build_pane_path() {
local pane_home="$1"
local runtime_bin="${2:-}"
local system_path="${3:-/usr/local/bin:/usr/bin:/bin}"
local prefix
prefix=$(mosaic_fleet_build_runtime_bin_prefix "$pane_home" "$runtime_bin")
printf '%s' "${prefix:+${prefix}:}${system_path}"
}
mosaic_fleet_resolve_in_pane_path() {
local pane_path="$1"
local binary="$2"
PATH="$pane_path" command -v -- "$binary" 2>/dev/null
}
# Sets executable evidence in MOSAIC_FLEET_EXECUTABLE_* and returns nonzero when
# a resolved script's shebang interpreter cannot run in the pane. Native/ELF
# binaries have no PATH-resolved interpreter dependency and pass the executable
# bit check. Node receives an additional side-effect-free `node --version`
# execution check; invoking `mosaic --version` itself is intentionally avoided
# because Mosaic performs a cache-writing/network update check at CLI startup.
mosaic_fleet_check_resolved_executable() {
local pane_path="$1"
local resolved="$2"
MOSAIC_FLEET_EXECUTABLE_DEPENDENCY=""
MOSAIC_FLEET_EXECUTABLE_PROBE=""
MOSAIC_FLEET_EXECUTABLE_EXIT=""
MOSAIC_FLEET_EXECUTABLE_OUTPUT=""
[ -x "$resolved" ] || {
MOSAIC_FLEET_EXECUTABLE_OUTPUT="resolved path is not executable"
return 70
}
local magic=""
IFS= read -r -n 2 magic < "$resolved" || true
[ "$magic" = '#!' ] || return 0
local shebang
IFS= read -r shebang < "$resolved" || true
shebang=${shebang%$'\r'}
shebang=${shebang#\#!}
local parts=()
read -r -a parts <<< "$shebang"
local interpreter="${parts[0]:-}"
[[ "$interpreter" = /* ]] && [ -x "$interpreter" ] || {
MOSAIC_FLEET_EXECUTABLE_DEPENDENCY="$interpreter"
MOSAIC_FLEET_EXECUTABLE_OUTPUT="shebang interpreter is absent or not executable"
return 70
}
local dependency="$interpreter"
local dependency_path="$interpreter"
if [ "${interpreter##*/}" = env ]; then
local index=1
if [ "${parts[$index]:-}" = -S ]; then index=$((index + 1)); fi
dependency="${parts[$index]:-}"
if [ -z "$dependency" ] || [[ "$dependency" = -* ]]; then
MOSAIC_FLEET_EXECUTABLE_DEPENDENCY="$dependency"
MOSAIC_FLEET_EXECUTABLE_OUTPUT="unsupported env shebang"
return 70
fi
fi
MOSAIC_FLEET_EXECUTABLE_DEPENDENCY="$dependency"
if [ "${dependency##*/}" = node ]; then
MOSAIC_FLEET_EXECUTABLE_PROBE="node --version"
fi
if [ "${interpreter##*/}" = env ]; then
if ! dependency_path=$(mosaic_fleet_resolve_in_pane_path "$pane_path" "$dependency"); then
MOSAIC_FLEET_EXECUTABLE_OUTPUT="shebang command is not on the pane PATH"
return 70
fi
fi
if [ "${dependency##*/}" = node ]; then
if MOSAIC_FLEET_EXECUTABLE_OUTPUT=$(PATH="$pane_path" "$dependency_path" --version 2>&1); then
MOSAIC_FLEET_EXECUTABLE_EXIT=0
else
MOSAIC_FLEET_EXECUTABLE_EXIT=$?
return 70
fi
fi
return 0
}
mosaic_fleet_runtime_path_main() {
local mosaic_home=""
local runtime_bin=""
local system_path="/usr/local/bin:/usr/bin:/bin"
local binary=""
local check_executable=0
while [ "$#" -gt 0 ]; do
case "$1" in
--mosaic-home)
[ "$#" -ge 2 ] || return 64
mosaic_home="$2"
shift 2
;;
--runtime-bin)
[ "$#" -ge 2 ] || return 64
runtime_bin="$2"
shift 2
;;
--binary)
[ "$#" -ge 2 ] || return 64
binary="$2"
shift 2
;;
--check-executable)
check_executable=1
shift
;;
# Test seam for measuring a greenfield host with no system Node. The
# launcher and production CLI omit it and retain the fixed system suffix.
--system-path)
[ "$#" -ge 2 ] || return 64
system_path="$2"
shift 2
;;
*) return 64 ;;
esac
done
[ -n "$mosaic_home" ] && [ -n "$binary" ] || return 64
local pane_home pane_path resolved
pane_home=$(mosaic_fleet_pane_home "$mosaic_home" "${HOME:-}")
# npm config is HOME-sensitive. Pin it to the derived pane home before asking
# for its prefix so an operator's unrelated npmrc cannot influence preflight.
HOME=$pane_home
export HOME
pane_path=$(mosaic_fleet_build_pane_path "$pane_home" "$runtime_bin" "$system_path")
if ! resolved=$(mosaic_fleet_resolve_in_pane_path "$pane_path" "$binary"); then
printf 'pane_path\0%s\0status\0missing\0binary_path\0\0dependency\0\0probe_command\0\0probe_exit\0\0probe_output\0\0' \
"$pane_path"
return 69
fi
if [ "$check_executable" -eq 1 ]; then
if mosaic_fleet_check_resolved_executable "$pane_path" "$resolved"; then
printf 'pane_path\0%s\0status\0present\0binary_path\0%s\0dependency\0%s\0probe_command\0%s\0probe_exit\0%s\0probe_output\0%s\0' \
"$pane_path" "$resolved" "$MOSAIC_FLEET_EXECUTABLE_DEPENDENCY" \
"$MOSAIC_FLEET_EXECUTABLE_PROBE" "$MOSAIC_FLEET_EXECUTABLE_EXIT" \
"$MOSAIC_FLEET_EXECUTABLE_OUTPUT"
return 0
fi
printf 'pane_path\0%s\0status\0unexecutable\0binary_path\0%s\0dependency\0%s\0probe_command\0%s\0probe_exit\0%s\0probe_output\0%s\0' \
"$pane_path" "$resolved" "$MOSAIC_FLEET_EXECUTABLE_DEPENDENCY" \
"$MOSAIC_FLEET_EXECUTABLE_PROBE" "$MOSAIC_FLEET_EXECUTABLE_EXIT" \
"$MOSAIC_FLEET_EXECUTABLE_OUTPUT"
return 70
fi
printf 'pane_path\0%s\0status\0present\0binary_path\0%s\0dependency\0\0probe_command\0\0probe_exit\0\0probe_output\0\0' \
"$pane_path" "$resolved"
return 0
}
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
set -euo pipefail
mosaic_fleet_runtime_path_main "$@"
fi