200 lines
6.6 KiB
Bash
Executable File
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
|