#!/bin/bash set -euo pipefail # Debian packages: # curl bash python3-minimal python3-venv python3-dev git make gcc g++ # CentOS packages: # curl bash python39 python39-devel git make gcc gcc-c++ # Fedora packages: # curl bash python3 python3-devel git make gcc gcc-c++ # Alpine packages: # curl bash python3 python3-dev git make gcc g++ linux-headers libffi-dev declare -a supported_versions=(python3.12 python3.11 python3.10 python3.9) declare -a debian_packages=(curl bash python3-minimal python3-venv python3-dev git make gcc g++) declare -a centos_packages=(curl bash python39 python39-devel git make gcc gcc-c++) declare -a fedora_packages=(curl bash python3 python3-devel git make gcc gcc-c++ findutils) declare -a alpine_packages=(curl bash python3 python3-dev git make gcc g++ linux-headers libffi-dev) declare install_path="$HOME/fixinventory" declare python_cmd declare git_install=false declare dev_mode=false declare unattended=false declare venv=true declare install_plugins=true declare branch=main main() { echo "fixinventory bootstrapper" if [ -f .git/config -a -d fixcore ]; then install_path="$PWD" fi local end_of_opt local positional=() while [[ $# -gt 0 ]]; do case "${end_of_opt:-}${1}" in -h|--help) usage 0 ;; --python) shift; python_cmd="${1:-}" ;; --path) shift; install_path="${1:-}" ;; --branch) shift; branch="${1:-}" ;; --no-venv) venv=false ;; --no-plugins) install_plugins=false ;; --dev) dev_mode=true ;; --git) git_install=true ;; --yes) unattended=true ;; --) end_of_opt=1 ;; -*) invalid "$1" ;; *) positional+=("$1") ;; esac if [ $# -gt 0 ]; then shift fi done if [ ${#positional[@]} -gt 0 ]; then set -- "${positional[@]}" fi install_path=${install_path%%+(/)} if [ -z "${install_path:-}" ]; then echo "Invalid install path $install_path" exit 1 fi if [ -z "${branch:-}" ]; then echo "Invalid branch" exit 1 fi if [ -z "${python_cmd:-}" ]; then python_cmd="$(find_python)" fi if [ -z "${python_cmd:-}" ]; then echo -e "Could not find a compatible Python interpreter!\nSupported versions are" "${supported_versions[@]}" exit 1 fi if ! type "$python_cmd" > /dev/null 2>&1; then echo -e "Unable to use Python interpreter $python_cmd" exit 1 fi CWD=$(pwd) echo "Using $python_cmd" ensure_install_path if [ "$venv" = true ]; then activate_venv "$python_cmd" fi cd "$CWD" ensure_pip if [ "$dev_mode" = true ]; then install_dev fi install_fixinventory if [ "$install_plugins" = true ]; then install_plugins fi echo -e "Install/Update completed.\nRun\n\tsource ${install_path}/venv/bin/activate\nto activate venv." } usage() { cat <<EOF Usage: $(basename "$0") [options] Valid options: -h, --help show this help message and exit --path <path> install directory (default: . if in fixinventory git repo else ~/fixinventory/) --python <path> Python binary to use (default: search for best match) --branch <branch> Git branch/tag to use (default: main) --dev install development dependencies (default: false) --yes unattended mode - assume yes for all questions (default: false) --no-venv do not create a Python venv for package installation (default: false) --git install from remote Git instead of local repo (default: false) EOF if [ -n "$1" ]; then exit "$1" fi } invalid() { echo "ERROR: Unrecognized argument: $1" >&2 usage 1 } ensure_install_path() { echo "Using install path $install_path" mkdir -p "$install_path" cd "$install_path" } find_python() { local version for version in "${supported_versions[@]}"; do if type "$version" > /dev/null 2>&1; then echo "$version" return 0 fi done } activate_venv() { local python_cmd=$1 if [ -d "venv/" ]; then echo -e "Virtual Python env already exists!\nRun\n\trm -rf venv/\nif you want to recreate it." else echo "Creating virtual Python env in venv/ using $python_cmd" "$python_cmd" -m venv venv --prompt "fixinventory venv" fi echo "Activating venv" source venv/bin/activate } ensure_pip() { echo "Ensuring Python pip is available and up to date." if ! python -m pip help > /dev/null 2>&1; then python -m ensurepip -q -U fi pip install -q -U pip wheel } install_dev() { echo "Installing development dependencies" if [ -f "requirements-all.txt" ]; then pip install -q -r "requirements-all.txt" else pip install -q -r "https://raw.githubusercontent.com/someengineering/fixinventory/main/requirements-all.txt" fi } install_fixinventory() { echo "Installing fixinventory" if [ -f "requirements-extra.txt" ]; then pip install -q -r "requirements-extra.txt" else pip install -q -r "https://raw.githubusercontent.com/someengineering/fixinventory/main/requirements-extra.txt" fi local fixinventory_components=(fixlib fixcore fixshell fixworker fixmetrics) for component in "${fixinventory_components[@]}"; do pip_install "$component" done } install_plugins() { local collector_plugins=(aws azure digitalocean dockerhub example_collector gcp github k8s onelogin posthog random scarf slack) for plugin in "${collector_plugins[@]}"; do pip_install "$plugin" true done } ensure_git() { if ! type git > /dev/null 2>&1; then echo "Git is not available in PATH - aborting install" exit 1 fi } pip_install() { local package=$1 local plugin=${2:-false} local egg_prefix="" local path_prefix="" if [ "$plugin" = true ]; then path_prefix="plugins/" egg_prefix="fix-plugin-" fi local package_name="${egg_prefix}${package}" package_name=${package_name//_/-} local relative_path="${path_prefix}${package}/" if [ -d "$relative_path" ] && [ "$git_install" = false ]; then echo "Installing $package_name editable from local path $relative_path" # until this is fixed: https://github.com/pypa/setuptools/issues/3518 pip install -q --editable "$relative_path" --config-settings editable_mode=compat else ensure_git local git_repo="git+https://github.com/someengineering/fixinventory.git@${branch}#egg=${package_name}&subdirectory=${relative_path}" echo "Installing $package_name from remote $git_repo" pip install -q -U "$git_repo" fi } main "$@"