#!/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                                                  |
# +-----------------------------------------------------------------------------------------------------+

#
#  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' (command-line processor to output JSON from a shell)
    and feed it to 'jq' (lightweight and flexible command-line JSON processor)

    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

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
