#!/bin/bash # Platform detection functions for Mosaic Stack installer # Provides OS, package manager, architecture, and environment detection # ============================================================================ # Colors (if terminal supports them) # ============================================================================ if [[ -t 1 ]]; then BOLD='\033[1m' ACCENT='\033[38;2;128;90;213m' SUCCESS='\033[38;2;47;191;113m' WARN='\033[38;2;255;176;32m' ERROR='\033[38;2;226;61;45m' INFO='\033[38;2;100;149;237m' MUTED='\033[38;2;139;127;119m' NC='\033[0m' else BOLD='' ACCENT='' SUCCESS='' WARN='' ERROR='' INFO='' MUTED='' NC='' fi # ============================================================================ # OS Detection # ============================================================================ # Detect operating system type # Returns: macos, debian, arch, fedora, linux, unknown detect_os() { case "$OSTYPE" in darwin*) echo "macos" return 0 ;; linux-gnu*) if [[ -f /etc/os-release ]]; then # shellcheck source=/dev/null source /etc/os-release case "$ID" in ubuntu|debian|linuxmint|pop|elementary) echo "debian" return 0 ;; arch|manjaro|endeavouros|garuda|arcolinux) echo "arch" return 0 ;; fedora|rhel|centos|rocky|almalinux|ol) echo "fedora" return 0 ;; *) echo "linux" return 0 ;; esac else echo "linux" return 0 fi ;; *) echo "unknown" return 1 ;; esac } # Detect if running under WSL (Windows Subsystem for Linux) # Returns WSL_DISTRO_NAME if in WSL, empty string otherwise detect_wsl() { if [[ -n "${WSL_DISTRO_NAME:-}" ]]; then echo "$WSL_DISTRO_NAME" return 0 fi # Check for WSL in /proc/version if [[ -f /proc/version ]]; then if grep -qi "microsoft\|wsl" /proc/version 2>/dev/null; then # Try to get distro name from os-release if [[ -f /etc/os-release ]]; then # shellcheck source=/dev/null source /etc/os-release echo "${NAME:-WSL}" return 0 fi echo "WSL" return 0 fi fi return 1 } # Check if running in WSL is_wsl() { [[ -n "${WSL_DISTRO_NAME:-}" ]] && return 0 [[ -f /proc/version ]] && grep -qi "microsoft\|wsl" /proc/version 2>/dev/null } # Get human-readable OS name get_os_name() { local os="$1" case "$os" in macos) echo "macOS" ;; debian) echo "Debian/Ubuntu" ;; arch) echo "Arch Linux" ;; fedora) echo "Fedora/RHEL" ;; linux) echo "Linux" ;; *) echo "Unknown" ;; esac } # ============================================================================ # Package Manager Detection # ============================================================================ # Detect the system's package manager # Returns: brew, apt, pacman, dnf, yum, unknown detect_package_manager() { local os="$1" # First check for Homebrew (available on macOS and Linux) if command -v brew &>/dev/null; then echo "brew" return 0 fi # Fall back to OS-specific package managers case "$os" in macos) # macOS without Homebrew echo "none" return 1 ;; debian) if command -v apt-get &>/dev/null; then echo "apt" return 0 fi ;; arch) if command -v pacman &>/dev/null; then echo "pacman" return 0 fi ;; fedora) if command -v dnf &>/dev/null; then echo "dnf" return 0 elif command -v yum &>/dev/null; then echo "yum" return 0 fi ;; esac echo "unknown" return 1 } # ============================================================================ # Architecture Detection # ============================================================================ # Get system architecture # Returns: x86_64, aarch64, armv7l, armv6l, unknown get_arch() { local arch arch=$(uname -m) case "$arch" in x86_64|amd64) echo "x86_64" ;; aarch64|arm64) echo "aarch64" ;; armv7l|armhf) echo "armv7l" ;; armv6l) echo "armv6l" ;; *) echo "unknown" ;; esac } # Check if running on Apple Silicon is_apple_silicon() { [[ "$(detect_os)" == "macos" ]] && [[ "$(get_arch)" == "aarch64" ]] } # ============================================================================ # Init System Detection # ============================================================================ # Detect the init system # Returns: systemd, openrc, launchd, sysvinit, unknown detect_init_system() { local os os=$(detect_os) case "$os" in macos) echo "launchd" return 0 ;; esac # Check for systemd if command -v systemctl &>/dev/null && pidof systemd &>/dev/null; then echo "systemd" return 0 fi # Check for OpenRC if command -v rc-status &>/dev/null; then echo "openrc" return 0 fi # Check for SysVinit if command -v service &>/dev/null; then echo "sysvinit" return 0 fi echo "unknown" return 1 } # ============================================================================ # Privilege Helpers # ============================================================================ # Check if running as root is_root() { [[ "$(id -u)" -eq 0 ]] } # Run command with sudo only if not already root maybe_sudo() { if is_root; then # Skip -E flag when root (env is already preserved) if [[ "${1:-}" == "-E" ]]; then shift fi "$@" else sudo "$@" fi } # Ensure sudo is available (Linux only) require_sudo() { local os os=$(detect_os) if [[ "$os" == "macos" ]]; then return 0 fi if is_root; then return 0 fi if command -v sudo &>/dev/null; then return 0 fi echo -e "${ERROR}Error: sudo is required for system installs on Linux${NC}" echo "Install sudo or re-run as root." return 1 } # Validate sudo credentials (cache them early) validate_sudo() { if is_root; then return 0 fi if command -v sudo &>/dev/null; then sudo -v 2>/dev/null return $? fi return 1 } # ============================================================================ # TTY and Interactive Detection # ============================================================================ # Check if running in an interactive terminal is_interactive() { [[ -t 0 && -t 1 ]] } # Check if we can prompt the user is_promptable() { [[ -r /dev/tty && -w /dev/tty ]] } # Read input from TTY (for prompts when stdin is piped) read_from_tty() { local prompt="$1" local var_name="$2" if is_promptable; then echo -e "$prompt" > /dev/tty read -r "$var_name" < /dev/tty else return 1 fi } # ============================================================================ # Shell Detection # ============================================================================ # Get current shell name get_shell() { basename "${SHELL:-/bin/sh}" } # Get shell configuration file get_shell_rc() { local shell shell=$(get_shell) case "$shell" in zsh) echo "$HOME/.zshrc" ;; bash) echo "$HOME/.bashrc" ;; fish) echo "$HOME/.config/fish/config.fish" ;; *) echo "$HOME/.profile" ;; esac } # ============================================================================ # Network and Connectivity # ============================================================================ # Check if we have internet connectivity has_internet() { local timeout="${1:-5}" # Try to reach common endpoints if command -v curl &>/dev/null; then curl -s --max-time "$timeout" https://api.github.com &>/dev/null return $? elif command -v wget &>/dev/null; then wget -q --timeout="$timeout" --spider https://api.github.com return $? fi # Fall back to ping ping -c 1 -W "$timeout" 8.8.8.8 &>/dev/null 2>&1 } # Get local IP address get_local_ip() { local ip # Try hostname command first (macOS) if command -v hostname &>/dev/null; then ip=$(hostname -I 2>/dev/null | cut -d' ' -f1) if [[ -n "$ip" ]]; then echo "$ip" return 0 fi fi # Try ip command (Linux) if command -v ip &>/dev/null; then ip=$(ip route get 1 2>/dev/null | awk '{print $7; exit}') if [[ -n "$ip" ]]; then echo "$ip" return 0 fi fi # Try ifconfig if command -v ifconfig &>/dev/null; then ip=$(ifconfig 2>/dev/null | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | head -1) if [[ -n "$ip" ]]; then echo "$ip" return 0 fi fi return 1 } # ============================================================================ # System Resources # ============================================================================ # Get total RAM in MB get_total_ram() { local os os=$(detect_os) case "$os" in macos) sysctl -n hw.memsize 2>/dev/null | awk '{print int($1/1024/1024)}' ;; *) if [[ -f /proc/meminfo ]]; then awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo else echo "0" fi ;; esac } # Get available disk space in GB for a path get_available_disk() { local path="${1:-.}" if command -v df &>/dev/null; then df -BG "$path" 2>/dev/null | awk 'NR==2 {print $4}' | tr -d 'G' else echo "0" fi } # Check if system meets minimum requirements check_minimum_requirements() { local min_ram="${1:-2048}" # MB local min_disk="${2:-10}" # GB local ram disk ram=$(get_total_ram) disk=$(get_available_disk "$HOME") local errors=() if [[ "$ram" -lt "$min_ram" ]]; then errors+=("RAM: ${ram}MB (minimum: ${min_ram}MB)") fi if [[ "$disk" -lt "$min_disk" ]]; then errors+=("Disk: ${disk}GB (minimum: ${min_disk}GB)") fi if [[ ${#errors[@]} -gt 0 ]]; then echo "System does not meet minimum requirements:" printf ' - %s\n' "${errors[@]}" return 1 fi return 0 } # ============================================================================ # Downloader Detection # ============================================================================ DOWNLOADER="" detect_downloader() { if command -v curl &>/dev/null; then DOWNLOADER="curl" return 0 fi if command -v wget &>/dev/null; then DOWNLOADER="wget" return 0 fi echo -e "${ERROR}Error: Missing downloader (curl or wget required)${NC}" return 1 } # Download a file securely download_file() { local url="$1" local output="$2" if [[ -z "$DOWNLOADER" ]]; then detect_downloader || return 1 fi if [[ "$DOWNLOADER" == "curl" ]]; then curl -fsSL --proto '=https' --tlsv1.2 --retry 3 --retry-delay 1 --retry-connrefused -o "$output" "$url" return $? fi wget -q --https-only --secure-protocol=TLSv1_2 --tries=3 --timeout=20 -O "$output" "$url" return $? } # Download and execute a script run_remote_script() { local url="$1" local tmp tmp=$(mktemp) if ! download_file "$url" "$tmp"; then rm -f "$tmp" return 1 fi /bin/bash "$tmp" local ret=$? rm -f "$tmp" return $ret } # ============================================================================ # PATH Management # ============================================================================ # Store original PATH for later comparison ORIGINAL_PATH="${PATH:-}" # Check if a directory is in PATH path_has_dir() { local path="$1" local dir="${2%/}" [[ -z "$dir" ]] && return 1 case ":${path}:" in *":${dir}:"*) return 0 ;; *) return 1 ;; esac } # Add directory to PATH in shell config add_to_path() { local dir="$1" local rc="${2:-$(get_shell_rc)}" if [[ ! -f "$rc" ]]; then return 1 fi # shellcheck disable=SC2016 local path_line="export PATH=\"${dir}:\$PATH\"" if ! grep -qF "$dir" "$rc" 2>/dev/null; then echo "" >> "$rc" echo "# Added by Mosaic Stack installer" >> "$rc" echo "$path_line" >> "$rc" return 0 fi return 1 } # Warn about missing PATH entries warn_path_missing() { local dir="$1" local label="$2" if [[ -z "$dir" ]]; then return 0 fi if path_has_dir "$ORIGINAL_PATH" "$dir"; then return 0 fi echo "" echo -e "${WARN}→${NC} PATH warning: missing ${label}: ${INFO}${dir}${NC}" echo -e "This can make commands show as \"not found\" in new terminals." echo -e "Fix by adding to your shell config:" echo -e " export PATH=\"${dir}:\$PATH\"" } # ============================================================================ # Temp File Management # ============================================================================ TMPFILES=() # Create a tracked temp file create_temp_file() { local f f=$(mktemp) TMPFILES+=("$f") echo "$f" } # Create a tracked temp directory create_temp_dir() { local d d=$(mktemp -d) TMPFILES+=("$d") echo "$d" } # Cleanup all temp files cleanup_temp_files() { local f for f in "${TMPFILES[@]:-}"; do rm -rf "$f" 2>/dev/null || true done } # Set up cleanup trap setup_cleanup_trap() { trap cleanup_temp_files EXIT } # ============================================================================ # Platform Summary # ============================================================================ # Print a summary of the detected platform print_platform_summary() { local os pkg arch init wsl os=$(detect_os) pkg=$(detect_package_manager "$os") arch=$(get_arch) init=$(detect_init_system) wsl=$(detect_wsl) echo -e "${BOLD}Platform Detection:${NC}" echo -e " OS: ${INFO}$(get_os_name "$os")${NC}" echo -e " Architecture: ${INFO}$arch${NC}" echo -e " Package Mgr: ${INFO}$pkg${NC}" echo -e " Init System: ${INFO}$init${NC}" if [[ -n "$wsl" ]]; then echo -e " WSL: ${INFO}$wsl${NC}" fi echo -e " Shell: ${INFO}$(get_shell)${NC}" echo -e " RAM: ${INFO}$(get_total_ram)MB${NC}" echo -e " Disk: ${INFO}$(get_available_disk)GB available${NC}" }