universal-os-detector.sh
#!/usr/bin/env bash

set -eo pipefail
trap 'log "Error encountered at line ${LINENO:-unknown} while executing command: ${BASH_COMMAND:-unknown}" "ERROR"' ERR
trap 'log "Received termination signal. Exiting." "WARN"; cleanup' SIGINT SIGTERM

#### LOGGING

log_file=${log_file:-~/universal-os-detector.log}

console_log_level=${console_log_level:-${CONSOLE_LOG_LEVEL:-1}}

color_error='\033[0;31m'
color_warn='\033[0;33m'
color_info='\033[0;30m'
color_system='\033[0;37m'
color_reset='\033[0m'
color_debug='\033[0;36m'

get_log_color() {
    local level="$1"
    case "$level" in
        error) echo "$color_error" ;;
        warn) echo "$color_warn" ;;
        info) echo "$color_info" ;;
        system) echo "$color_system" ;;
        debug) echo "$color_debug" ;;
        *) echo "$color_reset" ;;
    esac
}

print_log_message() {
    local color="$1"
    local level="$2"
    local message="$3"
    local current_time="$4"

    echo -e "${color}$current_time [$level]: $message${COLOR_RESET}"
}

lowercase() {
    echo "$1" | tr '[:upper:]' '[:lower:]'
}

uppercase() {
    echo "$1" | tr '[:lower:]' '[:upper:]'
}

should_log_to_console() {
    local level="$1"
    local lower_level
    lower_level=$(lowercase "$level")

    case "$console_log_level" in
        0) return 1 ;;
        1) [[ "$lower_level" =~ ^(system|warn|error)$ ]] && return 0 || return 1 ;;
        2) [[ "$lower_level" =~ ^(system|warn|error|info)$ ]] && return 0 || return 1 ;;
        3) return 0 ;;
        *) return 1 ;;
    esac
}

log() {
    local message="${1:-}"
    local level="${2:-info}"
    local current_time=$(date '+%Y-%m-%d %H:%M:%S')
    
    local color
    color=$(get_log_color "$level")

    local upper_level
    upper_level=$(uppercase "$level")

    echo -e "$current_time [$upper_level]: $message" >> "$log_file"

    if should_log_to_console "$level"; then
        print_log_message "$color" "$upper_level" "$message" "$current_time"
    fi
}

#### LOGGING LEVEL VALIDATION

get_log_level() {
    local input="$1"
    local lower_case_input=$(lowercase "$input")

    if [[ "$input" =~ ^[0-3]$ ]]; then
        echo "$input"
    else
        case "$lower_case_input" in
            none|n) echo 0 ;;
            default|d) echo 1 ;;
            verbose|v) echo 2 ;;
            debug|deb) echo 3 ;;
            *) echo -1 ;;
        esac
    fi
}

get_text_log_level() {
    local level_numeric="$1"
    case "$level_numeric" in
        0) echo "NONE" ;;
        1) echo "DEFAULT" ;;
        2) echo "VERBOSE" ;;
        3) echo "DEBUG" ;;
        *) echo "UNKNOWN" ;;
    esac
}

handle_invalid_log_level_error() {
    local error_message="Error: Invalid console_log_level. Please use one of the following valid options: N/NONE/0, D/DEFAULT/1, V/VERBOSE/2, or DEB/DEBUG/3."
    echo -e "${color_error}$error_message${color_reset}" >&2
    echo -e "$(date '+%Y-%m-%d %H:%M:%S') [UNKNOWN]: $error_message" >> "$log_file"
    exit 1
}

validate_console_log_level() {
    local level_numeric
    local level_text
    local log_level_message

    level_numeric=$(get_log_level "$console_log_level")

    if [[ "$level_numeric" -eq -1 ]]; then
        handle_invalid_log_level_error
    fi

    level_text=$(get_text_log_level "$level_numeric")

    console_log_level="$level_numeric"
    log_level_message="Console log level is set to: $level_text [$console_log_level]"
    log "$log_level_message" info
}

#### FUNCTIONAL TESTS

check_command() {
    local required_command="$1"
    local version_flag="${2:---version}"
    local minimum_version="${3:-}"

    if ! command -v "$required_command" &>/dev/null; then
        log "Error: Command '$required_command' is not available." error
        return 1
    fi

    if [ -n "$minimum_version" ]; then
        current_version=$($required_command $version_flag 2>&1 | head -n 1 | grep -oE '[0-9]+(\.[0-9]+)+')
        if [ "$(printf '%s\n' "$minimum_version" "$current_version" | sort -V | head -n1)" != "$minimum_version" ]; then
            log "Error: Command '$required_command' version ($current_version) does not meet minimum version ($minimum_version)." error
            return 1
        fi
    fi
}

validate_log_file_access() {
    log "Verifying READ/WRITE permissions for log file: $log_file..." info

    if [ ! -f "$log_file" ]; then
        log "File $log_file does not exist." error
        return 1
    elif [ ! -r "$log_file" ]; then
        log "File $log_file is not readable." error
        return 1
    elif [ ! -w "$log_file" ]; then
        log "File $log_file is not writable." warn
        return 1
    fi

    return 0
}

validate_log_dir_access() {
    local log_dir=$(dirname "$log_file")
    
    log "Verifying READ/WRITE permissions for log file directory: $log_dir..." info
    
    if [ ! -d "$log_dir" ]; then
        log "Directory $log_dir does not exist." error
        return 1
    elif [ ! -r "$log_dir" ]; then
        log "Directory $log_dir is not readable." error
        return 1
    elif [ ! -w "$log_dir" ]; then
        log "Directory $log_dir is not writable." error
        return 1
    fi
    
    return 0
}

functional_tests() {
    log "Running functional tests..." info

    log "Checking command availability..." info
    check_command "uname" || exit 1
    check_command "tr" || exit 1
    check_command "grep" || exit 1

    validate_log_dir_access || exit 1
    validate_log_file_access || exit 1

    log "All functional tests passed." info
}

#### MAIN DETECTION LOGIC

#### CONTAINER

detect_container() {
    log "Detecting container environment..." info

    container=false
    container_name="None"

    if [ -f /.dockerenv ]; then
        container_name="Docker"
    elif grep -q 'libpod' /proc/1/cgroup 2>/dev/null; then
        container_name="Podman"
    elif grep -q '/kubepods' /proc/1/cgroup 2>/dev/null; then
        container_name="Kubernetes"
    elif grep -q 'lxc' /proc/1/cgroup 2>/dev/null; then
        container_name="LXC"
    elif grep -q 'VxID' /proc/self/status 2>/dev/null; then
        container_name="OpenVZ"
    elif grep -q 'docker\|containerd\|lxc' /proc/1/cgroup 2>/dev/null; then
        container_name="Generic Container"
    fi

    [ "$container_name" != "None" ] && container=true

    log "Container Environment: $container_name" system
}

#### OS

detect_os() {
    log "Detecting operating system..." info
    os_name=""

    uname_str=$(uname -s)
    
    case "$uname_str" in
        Linux)          os_name="Linux" ;;
        Darwin)         os_name="MacOS" ;;
        FreeBSD)        os_name="FreeBSD" ;;
        OpenBSD)        os_name="OpenBSD" ;;
        NetBSD)         os_name="NetBSD" ;;
        DragonFlyBSD)   os_name="DragonFlyBSD" ;;
        SunOS)          os_name="Solaris" ;;
        AIX)            os_name="AIX" ;;
        *)              log "Quick check did not detect a known OS, running detailed checks..." info ;;
    esac

    if [[ -n "$os_name" ]]; then
        log "Operating System: $os_name" system
        return
    fi

    if detect_linux_os || detect_windows_os || detect_macos_os || 
       detect_freebsd_os || detect_android_os || detect_ios_os || 
       detect_solaris_os || detect_aix_os || detect_freebsd_os || 
       detect_openbsd_os || detect_netbsd_os || detect_dragonflybsd_os; then
        log "Operating System: $os_name" system
    else
        log "Unable to detect OS." warn
        os_name="Unknown"
        log "Operating System: $os_name" system
    fi
}

detect_linux_os() {
    if grep -qi "linux" /proc/version 2>/dev/null || 
       [[ "$(cat /proc/sys/kernel/ostype 2>/dev/null)" == "Linux" ]] || 
       [[ -d /sys/module ]]; then
        os_name="Linux"
        return 0
    fi
    return 1
}

detect_windows_os() {
    if [[ -n "$WINDIR" ]] || 
       grep -qi microsoft /proc/version 2>/dev/null; then
        os_name="Windows"
        return 0
    elif [[ -f "/proc/version" && $(grep -qi 'wsl' /proc/version) ]]; then
        os_name="WSL"
        return 0
    elif [[ -f "/proc/version" && $(grep -qi 'cygwin' /proc/version) ]]; then
        os_name="Cygwin"
        return 0
    elif [[ -f "/proc/version" && $(grep -qi 'mingw' /proc/version) ]]; then
        os_name="MinGW"
        return 0
    fi
    return 1
}

detect_macos_os() {
    if [[ -d /System/Library/CoreServices ]] || 
       [[ -f /System/Library/CoreServices/SystemVersion.plist ]]; then
        os_name="MacOS"
        return 0
    fi
    return 1
}

detect_android_os() {
    if [[ -f "/system/build.prop" || -f "/data/system/packages.xml" ]] || 
       [[ -d "/system" && -d "/data" ]] || 
       [[ "$(getprop ro.build.version.release 2>/dev/null)" ]]; then
        os_name="Android"
        return 0
    fi
    return 1
}

detect_ios_os() {
    if [[ -d "/var/mobile" ]] || 
       [[ -f "/System/Library/CoreServices/SystemVersion.plist" ]] || 
       [[ -f "/usr/bin/ideviceinfo" ]]; then
        os_name="iOS"
        return 0
    fi
    return 1
}

detect_solaris_os() {
    if [[ -d "/usr/sbin" && -d "/usr/bin" ]] || 
       [[ -f "/etc/release" ]]; then
        os_name="Solaris"
        return 0
    fi
    return 1
}

detect_aix_os() {
    if [[ -f "/etc/os-release" && $(grep -qi "aix" /etc/os-release) ]] || 
       [[ -f "/etc/vmlinux" ]]; then
        os_name="AIX"
        return 0
    fi
    return 1
}

detect_freebsd_os() {
    if [[ -f /bin/freebsd-version ]] || [[ -d /boot/kernel ]]; then
        os_name="FreeBSD"
        return 0
    fi
    return 1
}

detect_openbsd_os() {
    if [[ -f /etc/version ]]; then
        os_name="OpenBSD"
        return 0
    fi
    return 1
}

detect_netbsd_os() {
    if [[ -f /etc/netbsd-version ]]; then
        os_name="NetBSD"
        return 0
    fi
    return 1
}

detect_dragonflybsd_os() {
    if [[ -f /bin/dfbsd-version ]]; then
        os_name="DragonFlyBSD"
        return 0
    fi
    return 1
}

### DIST

detect_linux_dist() {
    log "Detecting Linux distribution..." info
    if [ -f /etc/os-release ]; then
        linux_distro_name=$(grep '^NAME=' /etc/os-release | cut -d '"' -f 2)
    elif command -v lsb_release &>/dev/null; then
        linux_distro_name=$(lsb_release -si)
    else
        linux_distro_name="Unknown"
        log "Unable to detect Linux Distribution name." warn
    fi
    log "Linux Distribution Name: $linux_distro_name" system
}

### VERSION NUMBER

detect_version_number() {
    log "Detecting version number..." info

    case "$os_name" in
        MacOS)          detect_macos_ver_no ;;
        # Linux)        Base on detect_linux_dist TBA ;;
        Windows)        detect_windows_ver_no ;;
        WSL)            detect_wsl_ver_no ;;
        Cygwin)         detect_cygwin_ver_no ;;
        MinGW)          detect_mingw_ver_no ;;
        FreeBSD)        detect_freebsd_ver_no ;;
        OpenBSD)        detect_openbsd_ver_no ;;
        NetBSD)         detect_netbsd_ver_no ;;
        DragonflyBSD)   detect_dragonflybsd_ver_no ;;
        Solaris)        detect_solaris_ver_no ;;
        Android)        detect_android_ver_no ;;
        iOS)            detect_ios_ver_no ;;
        AIX)            detect_aix_ver_no ;;
        *)              log "Unable to detect version number for $os_name" info ;;
    esac
}

detect_macos_ver_no() {
    version_number=$(sw_vers -productVersion 2>/dev/null)
    [ -n "$version_number" ] && log "Version Number: $version_number" system || 
    log "Unable to detect version number." warn
}

detect_linux_ver_no() {
    if [ -f /etc/os-release ]; then
        version_number=$(grep -oP '^VERSION="\K[^"]+' /etc/os-release)
    elif command -v lsb_release &>/dev/null; then
        version_number=$(lsb_release -sr)
    elif [ -f /etc/redhat-release ]; then
        version_number=$(< /etc/redhat-release)
    elif [ -f /etc/debian_version ]; then
        version_number="Debian $(< /etc/debian_version)"
    fi
    [ -n "$version_number" ] && log "Version Number: $version_number" system || 
    log "Unable to detect version number." warn
}

detect_windows_ver_no() {
    version_number=$(powershell.exe -Command "(Get-WmiObject -Class Win32_OperatingSystem).Version" 2>/dev/null || 
    wmic os get Version | sed -n 2p || 
    cmd.exe /c ver)
    version_number=$(echo "$version_number" | tr -d '\r')
    [ -n "$version_number" ] && log "Version Number: $version_number" system || 
    log "Unable to detect version number." warn
}

detect_freebsd_ver_no() {
    version_number=$(grep -oP '^VERSION="\K[^"]+' /etc/os-release 2>/dev/null || 
    sysctl -n kern.version 2>/dev/null || 
    uname -r)
    [ -n "$version_number" ] && log "Version Number: $version_number" system || 
    log "Unable to detect version number." warn
}

detect_openbsd_ver_no() {
    version_number=$(uname -r || 
    dmesg | grep -oP 'OpenBSD \K[0-9]+\.[0-9]+' | head -n 1 || 
    sysctl -n kern.version 2>/dev/null)
    [ -n "$version_number" ] && log "Version Number: $version_number" system || 
    log "Unable to detect version number." warn
}

detect_netbsd_ver_no() {
    version_number=$(uname -r || 
    sysctl -n kern.version 2>/dev/null || 
    dmesg | grep -oP 'NetBSD \K[0-9]+\.[0-9]+' | head -n 1)
    [ -n "$version_number" ] && log "Version Number: $version_number" system || 
    log "Unable to detect version number." warn
}

detect_dragonflybsd_ver_no() {
    version_number=$(uname -r || 
    dmesg | grep -oP 'DragonFly v\K[0-9]+\.[0-9]+' | head -n 1 || 
    sysctl -n kern.version 2>/dev/null)
    [ -n "$version_number" ] && log "Version Number: $version_number" system || 
    log "Unable to detect version number." warn
}

detect_wsl_ver_no() {
    version_number=$(wsl.exe --version 2>/dev/null | grep -oP 'WSL \K[0-9]+(\.[0-9]+)?' || 
    uname -r | grep -oP 'Microsoft \K[0-9]+\.[0-9]+' || 
    powershell.exe -Command "[System.Environment]::OSVersion.Version" 2>/dev/null)
    [ -n "$version_number" ] && log "Version Number: $version_number" system || 
    log "Unable to detect version number." warn
}

detect_cygwin_ver_no() {
    version_number=$(cygcheck -V 2>/dev/null | grep -oP 'Cygwin \K[0-9]+\.[0-9]+' || 
                     uname -r | grep -oP 'cygwin \K[0-9]+\.[0-9]+' || 
                     setup-x86.exe --version 2>/dev/null)
    [ -n "$version_number" ] && log "Version Number: $version_number" system || 
    log "Unable to detect version number." warn
}

detect_mingw_ver_no() {
    version_number=$(gcc --version 2>/dev/null | grep -oP 'gcc \(MinGW\) \K[0-9]+\.[0-9]+' || 
                    mingw-get --version 2>/dev/null | grep -oP 'MinGW \K[0-9]+\.[0-9]+' || 
                    uname -r | grep -oP 'mingw-w64 \K[0-9]+\.[0-9]+')
    [ -n "$version_number" ] && log "Version Number: $version_number" system || 
    log "Unable to detect version number." warn
}

detect_solaris_ver_no() {
    version_number=$(grep -oP '^Oracle Solaris.*:\K.*' /etc/release 2>/dev/null || uname -r)
    [ -n "$version_number" ] && log "Version Number: $version_number" system || 
    log "Unable to detect version number." warn
}

detect_android_ver_no() {
    version_number=$(getprop ro.build.version.release 2>/dev/null)
    [ -n "$version_number" ] && log "Version Number: $version_number" system || 
    log "Unable to detect version number." warn
}

detect_ios_ver_no() {
    version_number=$(ideviceinfo -k ProductVersion 2>/dev/null)
    [ -n "$version_number" ] && log "Version Number: $version_number" system || 
    log "Unable to detect version number." warn
}

detect_aix_ver_no() {
    version_number=$(oslevel 2>/dev/null)
    [ -n "$version_number" ] && log "Version Number: $version_number" system || 
    log "Unable to detect version number." warn
}


### VERSION NAME

detect_version_name() {
    log "Detecting version name..." info

    case "$os_name" in
        MacOS)          map_macos_ver_name ;;
        # Linux)        Need to detect Distribution first, fix TBA ;;
        Windows)        map_win_ver_name ;;
        # WSL)          TBA ;;
        # Cygwin)       TBA ;;
        # MinGW)        TBA ;;
        # FreeBSD)      TBA ;;
        # OpenBSD)      TBA ;;
        # NetBSD)       TBA ;;
        # DragonflyBSD) TBA ;;
        Solaris)        map_solaris_ver_name ;;
        Android)        map_android_ver_name ;;
        # iOS)          Numbers only fix TBA ;;
        # AIX)          Numbers only fix TBA ;;
        *)              log "No version name available for $os_name" info ;;
    esac
}

map_macos_ver_name() {
    case "$version_number" in
        15.*) version_name="Sequoia" ;;
        14.*) version_name="Sonoma" ;;
        13.*) version_name="Ventura" ;;
        12.*) version_name="Monterey" ;;
        11.*) version_name="Big Sur" ;;
        10.15*) version_name="Catalina" ;;
        10.14*) version_name="Mojave" ;;
        10.13*) version_name="High Sierra" ;;
        10.12*) version_name="Sierra" ;;
        10.11*) version_name="El Capitan" ;;
        10.10*) version_name="Yosemite" ;;
        *) 
            version_name="Unknown"
            log "Unable to map version number to version name." warn
            ;;
    esac
    log "Version Name: $version_name" system
}

map_win_ver_name() {
    case "$version_number" in
        11.0.*) version_name="Windows 11" ;;
        10.0.*) version_name="Windows 10" ;;
        6.3.*) version_name="Windows 8.1" ;;
        6.2.*) version_name="Windows 8" ;;
        6.1.*) version_name="Windows 7" ;;
        6.0.*) version_name="Windows Vista" ;;
        5.1.*) version_name="Windows XP" ;;
        5.0.*) version_name="Windows 2000" ;;
        4.0.*) version_name="Windows NT 4.0" ;;
        3.51.*) version_name="Windows NT 3.51" ;;
        3.5.*) version_name="Windows NT 3.5" ;;
        3.1.*) version_name="Windows 3.1" ;;
        2.0.*) version_name="Windows 2.0" ;;
        1.0.*) version_name="Windows 1.0" ;;
        *) 
            version_name="Unknown"
            log "Unable to map version number to version name." warn
            ;;
    esac
    log "Version Name: $version_name" system
}

map_solaris_ver_name() {
    case "$version_number" in
        11.*) version_name="Oracle Solaris 11" ;;
        10.*) version_name="Oracle Solaris 10" ;;
        5.11) version_name="SunOS 5.11" ;;
        5.10) version_name="SunOS 5.10" ;;
        5.9)  version_name="SunOS 5.9" ;;
        5.8)  version_name="SunOS 5.8" ;;
        *) 
            version_name="Unknown"
            log "Unable to map version number to version name." warn
            ;;
    esac
    log "Version Name: $version_name" system
}

map_android_ver_name() {
    case "$version_number" in
        14.*) version_name="Upside Down Cake" ;;
        13.*) version_name="Tiramisu" ;;
        12.*) version_name="Snow Cone" ;;
        11.*) version_name="Red Velvet Cake" ;;
        10.*) version_name="Quince Tart" ;;
        9.*)  version_name="Pie" ;;
        8.*)  version_name="Oreo" ;;
        7.*)  version_name="Nougat" ;;
        6.*)  version_name="Marshmallow" ;;
        5.*)  version_name="Lollipop" ;;
        4.4*) version_name="KitKat" ;;
        4.3*) version_name="Jelly Bean" ;;
        4.2*) version_name="Jelly Bean" ;;
        4.1*) version_name="Jelly Bean" ;;
        4.0*) version_name="Ice Cream Sandwich" ;;
        3.*)  version_name="Honeycomb" ;;
        2.3*) version_name="Gingerbread" ;;
        2.2*) version_name="FroYo" ;;
        2.1*) version_name="Eclair" ;;
        1.6)  version_name="Donut" ;;
        1.5)  version_name="Cupcake" ;;
        *) 
            version_name="Unknown"
            log "Unable to map version number to version name." warn
            ;;
    esac
    log "Version Name: $version_name" system
}


#### DESKTOP ENV

detect_desktop_env() {
    log "Detecting desktop environment..." info

    case "$os_name" in
        Linux)
            detect_linux_desktop_env
            ;;
        Windows)
            detect_windows_desktop_env
            ;;
        MacOS|Android|iOS|AIX)
            log "Desktop Environment: $os_name" system
            ;;
        *)
            log "Unsupported operating system: $os_name" error
            ;;
    esac
}

detect_linux_desktop_env() {
    local desktop_env="Unknown Linux desktop environment"

    if [ -n "$xdg_current_desktop" ]; then
        desktop_env="$xdg_current_desktop"
    elif [ -n "$desktop_session" ]; then
        desktop_env="$desktop_session"
    elif [ -n "$gdm_session" ]; then
        desktop_env="$gdm_session"
    else
        if command -v kdialog &>/dev/null; then
            desktop_env="KDE"
        elif command -v gnome-session &>/dev/null; then
            desktop_env="GNOME"
        elif command -v xfce4-session &>/dev/null; then
            desktop_env="Xfce"
        elif command -v mate-session &>/dev/null; then
            desktop_env="MATE"
        elif command -v cinnamon-session &>/dev/null; then
            desktop_env="Cinnamon"
        elif command -v lxsession &>/dev/null; then
            if command -v lxqt-session &>/dev/null; then
                desktop_env="LXQt"
            else
                desktop_env="LXDE"
            fi
        elif command -v pantheon-session &>/dev/null; then
            desktop_env="Pantheon"
        elif command -v enlightenment_start &>/dev/null; then
            desktop_env="Enlightenment"
        elif command -v deepin-session &>/dev/null; then
            desktop_env="Deepin"
        else
            log "No known Linux desktop environment binaries found." warn
        fi
    fi
    log "Linux Desktop Environment: $desktop_env" system
}

detect_windows_desktop_env() {
    local desktop_env="Unknown Windows desktop environment"

    if grep -qi microsoft /proc/version 2>/dev/null; then
        desktop_env="WSL (Windows Subsystem for Linux)"
    elif [[ "$ostype" == "msys"* ]]; then
        desktop_env="Git Bash"
    elif [[ "$ostype" == "cygwin"* ]]; then
        desktop_env="Cygwin"
    elif command -v powershell.exe &>/dev/null; then
        desktop_env="PowerShell"
    else
        desktop_env="Command Prompt (or unknown Windows shell)"
    fi

    log "Windows Desktop Environment: $desktop_env" system
}

#### ARCH

detect_arch() {
    log "Detecting architecture..." info
    arch=$(uname -m || echo "Unknown architecture")

    case "$arch" in
        x86_64) arch="x86_64 (64-bit)" ;;
        i*86) arch="x86 (32-bit)" ;;
        armv6l|armv7l) arch="ARM (32-bit)" ;;
        aarch64) arch="ARM (64-bit)" ;;
        ppc64le) arch="PowerPC 64-bit (little-endian)" ;;
        riscv64) arch="RISC-V (64-bit)" ;;
        arm*)
            if [[ "$OSTYPE" == "darwin"* ]]; then
                arch="iOS (ARM)"
            else
                arch="Android (ARM)"
            fi ;;
        rs6000) arch="AIX (PowerPC)" ;;
        *) arch="Unknown Architecture" 
           log "Unknown architecture detected: $arch" warn ;;
    esac

    log "Architecture: $arch" system
}

#### KERNEL

detect_kernel() {
    log "Detecting kernel version..." info

    kernel=$(uname -r || echo "Unknown kernel")

    if [[ "$OSTYPE" == "android"* ]]; then
        kernel_fallback=$(getprop | grep "ro.build.version.release" | awk -F "=" '{print $2}')
        kernel=${kernel:-$kernel_fallback}
    elif [[ "$OSTYPE" == "darwin"* ]]; then
        kernel_fallback=$(uname -v)
        kernel=${kernel:-$kernel_fallback}
    fi

    log "Kernel: $kernel" system
}

#### CLEANUP

cleanup() {
    log "Cleaning up resources..." info
    log "Removing log file..." info
    rm -f "$log_file".lock
    log "Cleanup completed" info
    log "Exiting..." info
    exit 0
}

#### MAIN

run_detection() {
    validate_console_log_level

    functional_tests

    log "Starting detection..." INFO

    detect_container
    detect_os

    if [[ "$os_name" == "Linux" ]]; then
        detect_linux_dist
    fi

    detect_version_number
    detect_version_name
    detect_desktop_env
    detect_arch
    detect_kernel

    log "Detection completed." INFO

    cleanup
}

run_detection