#!/usr/bin/env bash

# +-----------------------------------------------------------------------------------------------------+
# | Title        : ssh-facts                                                                            |
# | Description  : Get some facts about the remote system                                               |
# | Author       : Sven Wick <sven.wick@gmx.de>                                                         |
# | Contributors : Denis Meiswinkel                                                                     |
# | URL          : https://codeberg.org/vaporup/ssh-tools                                               |
# | Based On     : https://code.ungleich.ch/ungleich-public/cdist/src/branch/master/cdist/conf/explorer |
# |                https://serverfault.com/a/343678                                                     |
# |                https://stackoverflow.com/a/8057052                                                  |
# +-----------------------------------------------------------------------------------------------------+

VERSION="ssh-facts (ssh-tools) 1.9"

#
#  Usage/Help message
#

function usage() {

cat << EOF

    Usage: ${0##*/} [user@]hostname

    For further processing of the data
    you can use standard shell tools like awk, grep, sed
    or convert it to JSON with jo and then feed it to jq

    EXAMPLES:

        ${0##*/} 127.0.0.1

        ${0##*/} 127.0.0.1 | grep ^OS_VERSION | awk -F'=' '{ print \$2 }'

        ${0##*/} 127.0.0.1 | jo -p

        ${0##*/} 127.0.0.1 | jo | jq

        ${0##*/} 127.0.0.1 | jo | jq .OS_VERSION
EOF

}

if [[ -z $1 || $1 == "--help" ]]; then
    usage
    exit 1
fi

if [[ $1 == "--version" ]]; then
    echo "${VERSION}"
    exit
fi

ssh "$@" 'bash -s' 2>/dev/null <<'END' | sed 's/[[:space:]]*=[[:space:]]*/=/'

function _os() {

    if grep -q ^Amazon /etc/system-release 2>/dev/null; then
       echo amazon
       exit 0
    fi

    if [ -f /etc/arch-release ]; then
       echo archlinux
       exit 0
    fi

    if [ -f /etc/cdist-preos ]; then
       echo cdist-preos
       exit 0
    fi

    if [ -d /gnu/store ]; then
       echo guixsd
       exit 0
    fi

    ### Debian and derivatives
    if grep -q ^DISTRIB_ID=Ubuntu /etc/lsb-release 2>/dev/null; then
       echo ubuntu
       exit 0
    fi

    # devuan ascii has both devuan_version and debian_version, so we need to check devuan_version first!
    if [ -f /etc/devuan_version ]; then
       echo devuan
       exit 0
    fi

    if [ -f /etc/debian_version ]; then
       echo debian
       exit 0
    fi

    ###

    if [ -f /etc/gentoo-release ]; then
       echo gentoo
       exit 0
    fi

    if [ -f /etc/openwrt_version ]; then
        echo openwrt
        exit 0
    fi

    if [ -f /etc/owl-release ]; then
       echo owl
       exit 0
    fi

    ### Redhat and derivatives
    if grep -q ^Scientific /etc/redhat-release 2>/dev/null; then
        echo scientific
        exit 0
    fi

    if grep -q ^CentOS /etc/redhat-release 2>/dev/null; then
        echo centos
        exit 0
    fi

    if grep -q ^Fedora /etc/redhat-release 2>/dev/null; then
       echo fedora
       exit 0
    fi

    if grep -q ^Mitel /etc/redhat-release 2>/dev/null; then
       echo mitel
       exit 0
    fi

    if [ -f /etc/redhat-release ]; then
       echo redhat
       exit 0
    fi
    ###

    if [ -f /etc/SuSE-release ]; then
       echo suse
       exit 0
    fi

    if [ -f /etc/slackware-version ]; then
       echo slackware
       exit 0
    fi

    # Appliances

    if grep -q '^Check Point Gaia' /etc/cp-release 2>/dev/null; then
        echo checkpoint
        exit 0
    fi

    uname_s="$(uname -s)"

    # Assume there is no tr on the client -> do lower case ourselves
    case "$uname_s" in
       Darwin)
          echo macosx
          exit 0
       ;;
       NetBSD)
          echo netbsd
          exit 0
       ;;
       FreeBSD)
          echo freebsd
          exit 0
       ;;
       OpenBSD)
          echo openbsd
          exit 0
       ;;
       SunOS)
          echo solaris
          exit 0
       ;;
    esac

    if [ -f /etc/os-release ]; then
       # after sles15, suse don't provide an /etc/SuSE-release anymore, but there is almost no difference between sles and opensuse leap, so call it suse
       # shellcheck disable=SC1091
       if (. /etc/os-release && echo "${ID_LIKE}" | grep -q '\(^\|\ \)suse\($\|\ \)')
       then
          echo suse
          exit 0
       fi
       # already lowercase, according to:
       # https://www.freedesktop.org/software/systemd/man/os-release.html
       awk -F= '/^ID=/ { if ($2 ~ /^'"'"'(.*)'"'"'$/ || $2 ~ /^"(.*)"$/) { print substr($2, 2, length($2) - 2) } else { print $2 } }' /etc/os-release
       exit 0
    fi

    echo "Unknown OS" >&2
    exit 1


}

#
# os_version
#

function rc_getvar() {
   awk -F= -v varname="$2" '
      function unquote(s) {
         if (s ~ /^".*"$/ || s ~ /^'\''.*'\''$/)
            return substr(s, 2, length(s) - 2)
         else
            return s
      }
      $1 == varname { print unquote(substr($0, index($0, "=") + 1)) }' "$1"
}

function _os_version() {

    case "$(_os)"

    in
       amazon)
          cat /etc/system-release
       ;;
       archlinux)
          # empty, but well...
          cat /etc/arch-release
       ;;
       checkpoint)
           awk '{version=$NF; printf("%s\n", substr(version, 2))}' /etc/cp-release
        ;;
       debian)
          debian_version=$(cat /etc/debian_version)
          case $debian_version
          in
              testing/unstable)
                  # previous to Debian 4.0 testing/unstable was used
                  # cf. https://metadata.ftp-master.debian.org/changelogs/main/b/base-files/base-files_11_changelog
                  echo 3.99
                  ;;
              */sid)
                  # sid versions don't have a number, so we decode by codename:
                  case $(expr "$debian_version" : '\([a-z]\{1,\}\)/')
                  in
                      trixie) echo 12.99 ;;
                      bookworm) echo 11.99 ;;
                      bullseye) echo 10.99 ;;
                      buster) echo 9.99 ;;
                      stretch) echo 8.99 ;;
                      jessie) echo 7.99 ;;
                      wheezy) echo 6.99 ;;
                      squeeze) echo 5.99 ;;
                      lenny) echo 4.99 ;;
                      *) echo 99.99 ;;
                  esac
                  ;;
              *)
                  echo "$debian_version"
                  ;;
          esac
       ;;
       devuan)
          devuan_version=$(cat /etc/devuan_version)
          case ${devuan_version}
          in
             (*/ceres)
                # ceres versions don't have a number, so we decode by codename:
                case ${devuan_version}
                in
                   (daedalus/ceres) echo 4.99 ;;
                   (chimaera/ceres) echo 3.99 ;;
                   (beowulf/ceres) echo 2.99 ;;
                   (ascii/ceres) echo 1.99 ;;
                   (*) exit 1
                esac
                ;;
             (*)
                echo "${devuan_version}"
                ;;
          esac
       ;;
       fedora)
          cat /etc/fedora-release
       ;;
       gentoo)
          cat /etc/gentoo-release
       ;;
       macosx)
          # NOTE: Legacy versions (< 10.3) do not support options
          sw_vers | awk -F ':[ \t]+' '$1 == "ProductVersion" { print $2 }'
       ;;
       freebsd)
          # Apparently uname -r is not a reliable way to get the patch level.
          # See: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=251743
          if command -v freebsd-version >/dev/null 2>&1
          then
             # get userland version
             freebsd-version -u
          else
             # fallback to kernel release for FreeBSD < 10.0
             uname -r
          fi
       ;;
       *bsd|solaris)
          uname -r
       ;;
       openwrt)
          cat /etc/openwrt_version
       ;;
       owl)
          cat /etc/owl-release
       ;;
       redhat|centos|mitel|scientific)
          cat /etc/redhat-release
       ;;
       slackware)
          cat /etc/slackware-version
       ;;
       suse)
          if [ -f /etc/os-release ]; then
            cat /etc/os-release
          else
            cat /etc/SuSE-release
          fi
       ;;
       ubuntu)
          if command -v lsb_release >/dev/null 2>&1
          then
             lsb_release -sr
          elif test -r /usr/lib/os-release
          then
             # fallback to /usr/lib/os-release if lsb_release is not present (like
             # on minimized Ubuntu installations)
             rc_getvar /usr/lib/os-release VERSION_ID
          elif test -r /etc/lsb-release
          then
             # extract DISTRIB_RELEASE= variable from /etc/lsb-release on old
             # versions without /usr/lib/os-release.
             rc_getvar /etc/lsb-release DISTRIB_RELEASE
          fi
       ;;
       alpine)
           cat /etc/alpine-release
       ;;
    esac

}

function _uptime() {

    if command -v uptime >/dev/null; then
        uptime | awk -F'( |,|:)+' '{if ($7=="min") m=$6; else {if ($7~/^day/) {d=$6;h=$8;m=$9} else {h=$6;m=$7}}} {print d+0,"days,",h+0,"hours,",m+0,"minutes"}'
    fi

}

function _last_reboot() {

    if command -v last >/dev/null; then
        last reboot -F | head -1 | awk '{print $6,$7,$8,$9}'
    fi

}

function _cpu_cores() {

    os=$(_os)
    case "$os" in
        "macosx")
            sysctl -n hw.physicalcpu
        ;;

        "openbsd")
            sysctl -n hw.ncpuonline
        ;;

        "freebsd"|"netbsd")
            PATH=$(getconf PATH)
            sysctl -n hw.ncpu
        ;;

        *)
            if [ -r /proc/cpuinfo ]; then
                cores="$(grep "core id" /proc/cpuinfo | sort | uniq | wc -l)"
                if [ "${cores}" -eq 0 ]; then
                    cores="1"
                fi
                echo "$cores"
            fi
        ;;
    esac

}

function _cpu_sockets() {

    os=$(_os)
    case "$os" in

        "macosx")
            system_profiler SPHardwareDataType | grep "Number of Processors" | awk -F': ' '{print $2}'
        ;;

        *)
        if [ -r /proc/cpuinfo ]; then
            sockets="$(grep "physical id" /proc/cpuinfo | sort -u | wc -l)"
            if [ "${sockets}" -eq 0 ]; then
                sockets="$(grep -c "processor" /proc/cpuinfo)"
            fi
            echo "${sockets}"
        fi
        ;;
    esac

}

function _hostname() {

    if command -v hostname >/dev/null
    then
            hostname
    else
            uname -n
    fi

}

function _kernel_name() {

    uname -s

}

function _machine() {

    if command -v uname >/dev/null 2>&1 ; then
        uname -m
    fi

}

function _machine_type() {

    if [ -d "/proc/vz" ] && [ ! -d "/proc/bc" ]; then
        echo openvz
        exit
    fi

    if [ -e "/proc/1/environ" ] &&
        tr '\000' '\n' < "/proc/1/environ" | grep -Eiq '^container='; then
        echo lxc
        exit
    fi

    if [ -r /proc/cpuinfo ]; then
        # this should only exist on virtual guest machines,
        # tested on vmware, xen, kvm
        if grep -q "hypervisor" /proc/cpuinfo; then
            # this file is aviable in xen guest systems
            if [ -r /sys/hypervisor/type ]; then
                if grep -q -i "xen" /sys/hypervisor/type; then
                    echo virtual_by_xen
                    exit
                fi
            else
                if [ -r /sys/class/dmi/id/product_name ]; then
                    if grep -q -i 'vmware' /sys/class/dmi/id/product_name; then
                        echo "virtual_by_vmware"
                        exit
                    elif grep -q -i 'bochs' /sys/class/dmi/id/product_name; then
                        echo "virtual_by_kvm"
                        exit
                    elif grep -q -i 'virtualbox' /sys/class/dmi/id/product_name; then
                        echo "virtual_by_virtualbox"
                        exit
                    fi
                fi

                if [ -r /sys/class/dmi/id/sys_vendor ]; then
                    if grep -q -i 'qemu' /sys/class/dmi/id/sys_vendor; then
                        echo "virtual_by_kvm"
                        exit
                    fi
                fi

                if [ -r /sys/class/dmi/id/chassis_vendor ]; then
                    if grep -q -i 'qemu' /sys/class/dmi/id/chassis_vendor; then
                        echo "virtual_by_kvm"
                        exit
                    fi
                fi
            fi
            echo "virtual_by_unknown"
        else
            echo "physical"
        fi
    else
        echo "unknown"
    fi

}

function _memory() {

    os=$(_os)
    case "$os" in
        "macosx")
            echo "$(sysctl -n hw.memsize)/1024" | bc
        ;;

        "openbsd")
            echo "$(sysctl -n hw.physmem) / 1048576" | bc
        ;;

        *)
            if [ -r /proc/meminfo ]; then
                grep "MemTotal:" /proc/meminfo | awk '{print $2}'
            fi
        ;;
    esac

}

function _init() {

    uname_s="$(uname -s)"

    case "$uname_s" in
        Linux)
            (pgrep -P0 -l | awk '/^1[ \t]/ {print $2;}') || true
        ;;
        FreeBSD|OpenBSD)
            ps -o comm= -p 1 || true
        ;;
        *)
            # return a empty string as unknown value
            echo ""
        ;;
    esac

}

function _lsb_codename() {

    set +e
    case "$(_os)" in

       checkpoint)
           awk '{printf("%s\n", $(NF-1))}' /etc/cp-release
       ;;
       openwrt)
          # shellcheck disable=SC1091
          (. /etc/openwrt_release && echo "$DISTRIB_CODENAME")
       ;;
       *)
          lsb_release=$(command -v lsb_release)
          if [ -x "$lsb_release" ]; then
             $lsb_release --short --codename
          fi
       ;;
    esac

}

function _lsb_description() {

    set +e
    case "$(_os)" in

       checkpoint)
           cat /etc/cp-release
       ;;
       openwrt)
          # shellcheck disable=SC1091
          (. /etc/openwrt_release && echo "$DISTRIB_DESCRIPTION")
       ;;
       *)
          lsb_release=$(command -v lsb_release)
          if [ -x "$lsb_release" ]; then
             $lsb_release --short --description
          fi
       ;;
    esac

}

function _lsb_id() {

    set +e
    case "$(_os)" in

      checkpoint)
           echo "CheckPoint"
       ;;
       openwrt)
          # shellcheck disable=SC1091
          (. /etc/openwrt_release && echo "$DISTRIB_ID")
       ;;
       *)
          lsb_release=$(command -v lsb_release)
          if [ -x "$lsb_release" ]; then
             $lsb_release --short --id
          fi
       ;;
    esac

}

function _lsb_release() {

    set +e
    case "$(_os)" in

       checkpoint)
           sed /etc/cp-release -e 's/.* R\([1-9][0-9]*\)\.[0-9]*$/\1/'
       ;;
       openwrt)
          # shellcheck disable=SC1091
          (. /etc/openwrt_release && echo "$DISTRIB_RELEASE")
       ;;
       *)
          lsb_release=$(command -v lsb_release)
          if [ -x "$lsb_release" ]; then
             $lsb_release --short --release
          fi
       ;;
    esac

}

function _runlevel() {

    set +e
    executable=$(command -v runlevel)
    if [ -x "$executable" ]; then
       "$executable" | awk '{ print $2 }'
    fi

}

function _disks() {

    uname_s="$(uname -s)"

    case "$uname_s" in

        FreeBSD)
            sysctl -n kern.disks
        ;;
        OpenBSD)
            sysctl -n hw.disknames | grep -Eo '[lsw]d[0-9]+'
        ;;
        NetBSD)
            PATH=$(getconf PATH)
            sysctl -n hw.disknames | awk -v RS=' ' '/^[lsw]d[0-9]+/'
        ;;
        Linux)
            # list of major device numbers toexclude:
            #  ram disks, floppies, cdroms
            # https://www.kernel.org/doc/Documentation/admin-guide/devices.txt
            ign_majors='1 2 11'

            if command -v lsblk >/dev/null 2>&1
            then
                lsblk -e "$(echo "$ign_majors" | tr ' ' ',')" -dno name
            elif test -d /sys/block/
            then
                # shellcheck disable=SC2012
                ls -1 /sys/block/ \
                | awk -v ign_majors="$(echo "$ign_majors" | tr ' ' '|')" '
                    {
                      devfile = "/sys/block/" $0 "/dev"
                      getline devno < devfile
                      close(devfile)
                      if (devno !~ "^(" ign_majors "):") print
                    }'
            else
                echo "Don't know how to list disks on Linux without lsblk and sysfs." >&2
                echo 'If you can, please submit a patch.'>&2
            fi
        ;;
        *)
            printf "Don't know how to list disks for %s operating system.\n" "${uname_s}" >&2
            printf 'If you can please submit a patch\n' >&2
        ;;
    esac \
    | xargs

}

function _interfaces() {

    if command -v ip >/dev/null
    then
            ip -o link show | sed -n 's/^[0-9]\+: \(.\+\): <.*/\1/p'
    elif command -v ifconfig >/dev/null
    then
            ifconfig -a | sed -n -E 's/^(.*)(:[[:space:]]*flags=|Link encap).*/\1/p'
    fi \
     | sort -u \
     | xargs

}

function get_facts() {

    local function_name=${1}
    local label=$( echo "${function_name/_/}" | tr '[:lower:]' '[:upper:]' )
    local fact="$( ${function_name} )"

    [[ -n "${fact// }" ]] && echo "${label}=${fact}"

}

get_facts _os
get_facts _os_version
get_facts _uptime
get_facts _last_reboot
get_facts _cpu_cores
get_facts _cpu_sockets
get_facts _hostname
get_facts _kernel_name
get_facts _machine
get_facts _machine_type
get_facts _memory
get_facts _init
get_facts _lsb_codename
get_facts _lsb_description
get_facts _lsb_id
get_facts _lsb_release
get_facts _runlevel
get_facts _disks
get_facts _interfaces

END
