#!/usr/bin/env python3
# -*- coding: utf-8; py-indent-offset: 4 -*-
#
# Author:  Linuxfabrik GmbH, Zurich, Switzerland
# Contact: info (at) linuxfabrik (dot) ch
#          https://www.linuxfabrik.ch/
# License: The Unlicense, see LICENSE file.

# https://github.com/Linuxfabrik/monitoring-plugins/blob/main/CONTRIBUTING.md

"""See the check's README for more details."""

import argparse
import os
import re
import socket
import sys

import lib.args
import lib.base
import lib.db_mysql
import lib.disk
import lib.distro
import lib.dmidecode
import lib.human
import lib.net
import lib.shell
import lib.time
import lib.txt
from lib.globals import STATE_OK, STATE_UNKNOWN

try:
    import psutil

    HAVE_PSUTIL = True
except ImportError:
    HAVE_PSUTIL = False


__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
__version__ = '2026061401 / v6.0.0'

DESCRIPTION = """Collects and displays key system information: OS and kernel version, CPU
configuration (physical, logical, and usable cores plus frequency), RAM, disk count,
virtualization type, network interfaces, listening ports, systemd services and timers,
cron jobs, installed packages, and user accounts. Optionally queries dmidecode for
firmware and hardware details, and fetches the public IP address. This check is purely
informational and never raises alerts.
Requires root or sudo."""

DEFAULT_DMIDECODE = False
DEFAULT_INSECURE = False
DEFAULT_NO_PROXY = False
DEFAULT_PUBLIC_IP_URL = None
DEFAULT_TIMEOUT = 2


def _natural_key(s):
    """Case-insensitive natural sort key. Splits a string into alternating
    text and integer chunks so 'foo10' sorts after 'foo2' and 'MariaDB-server'
    interleaves with lowercase 'mariadb-...' instead of clustering at the top
    because of ASCII capital ordering.
    """
    return [
        int(part) if part.isdigit() else part
        for part in re.split(r'(\d+)', (s or '').lower())
    ]


def parse_args():
    """Parse command line arguments using argparse."""
    parser = argparse.ArgumentParser(description=DESCRIPTION)

    parser.add_argument(
        '-V',
        '--version',
        action='version',
        version=f'%(prog)s: v{__version__} by {__author__}',
    )

    parser.add_argument(
        '--dmidecode',
        help='Gather additional hardware information via the `dmidecode` command, '
        'such as system components, serial numbers, and BIOS revisions. '
        'Requires sudo permissions.',
        dest='DMIDECODE',
        action='store_true',
        default=DEFAULT_DMIDECODE,
    )

    parser.add_argument(
        '--insecure',
        help=lib.args.help('--insecure'),
        dest='INSECURE',
        action='store_true',
        default=DEFAULT_INSECURE,
    )

    parser.add_argument(
        '--no-proxy',
        help=lib.args.help('--no-proxy'),
        dest='NO_PROXY',
        action='store_true',
        default=DEFAULT_NO_PROXY,
    )

    parser.add_argument(
        '--public-ip-url',
        help='One or more comma-separated URLs to "what is my ip" online services '
        'for fetching the public IP address. '
        'Example: '
        '`https://ipv4.icanhazip.com,https://ipecho.net/plain,https://ipinfo.io/ip`. '
        'Default: %(default)s',
        dest='PUBLIC_IP_URL',
        default=DEFAULT_PUBLIC_IP_URL,
    )

    parser.add_argument(
        '--tags',
        help='Guess a list of tags to apply in Icinga Director (Linuxfabrik Basket config).',
        dest='TAGS',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '--timeout',
        help=lib.args.help('--timeout') + ' Default: %(default)s (seconds)',
        dest='TIMEOUT',
        type=int,
        default=DEFAULT_TIMEOUT,
    )

    args, _ = parser.parse_known_args()
    return args


def get_birthday():
    """Using various methods to determine install date."""
    # the age of a machine is usually determined by the birthday of the root file system `/`.
    # however, this does not work for cloud systems that are installed from pre-built images.
    # in this case, the age of the cloud-init data must be used.
    if os.path.exists('/var/lib/cloud/data/instance-id'):
        # cloud-init based VM
        birthday = os.stat('/var/lib/cloud/data/instance-id')
        return f'born {lib.time.epoch2iso(birthday.st_ctime)}. '
    # no way to do this in python - getting the birthday of a folder
    success, result = lib.shell.shell_exec(['stat', '/'])
    if not success:
        return 'born unknown. '
    stdout, _, _ = result
    birthday = ''
    for line in stdout.splitlines():
        if 'Birth' in line:
            birthday = line.replace('Birth: ', '')[1:11].strip()
            break
    return f'born {birthday}. '


def get_boot_mode():
    return 'UEFI boot, ' if os.path.isdir('/sys/firmware/efi') else 'BIOS boot, '


def get_crontab():
    """Returns all crontab items."""
    cmd = [
        'grep',
        '--dereference-recursive',
        '--no-filename',
        '--invert-match',
        r'\s*#',
        '/etc/crontab',
        '/etc/cron.d/',
        '/etc/anacrontab',
        '/var/spool/cron',
    ]
    success, result = lib.shell.shell_exec(cmd)
    if not success:
        return ''
    stdout, _, _ = result
    output = ''
    line_regex = re.compile(r'\S+=')
    for line in stdout.splitlines():
        line = line.strip()
        if len(line) > 0 and re.match(line_regex, line) is None:
            output = f'{output}{line}\n'
    if output:
        output = f'crontab:\n{output}\n'
    return output


def get_disks():
    """Returns system disks tuple "text, count": `'Disk nvme0n1 1.8T', 1`"""
    success, result = lib.shell.shell_exec(
        [
            'lsblk',
            '--nodeps',
            '--output',
            'NAME,SIZE',
            '--noheadings',
            '--include',
            '8,252,259',
        ]
    )
    if not success:
        return '', 0
    stdout, _, _ = result
    output = []
    for disk in stdout.strip().splitlines():
        # zRAM devices can appear in the output of lsblk, but we cannot do anything useful with them
        if disk.startswith('zram'):
            continue
        output.append(re.sub('\\s+', ' ', disk))
    return f'{lib.txt.pluralize("Disk", len(output))} {", ".join(output)}, ', len(
        output
    )


def get_hw_info():
    result = lib.dmidecode.get_data()
    if not result:
        return ''
    output = ''
    for _, value in result.items():
        if value['dminame'] == 'Base Board Information':
            d = 'Default string'
            output += (
                f'* Base Board: Type'
                f' {value.get("Type", "n/a").replace(d, "n/a")}'
                f' {value.get("Manufacturer", "n/a").replace(d, "n/a")}'
                f' {value.get("Product Name", "n/a").replace(d, "n/a")}'
                f', SerNo {value.get("Serial Number", "n/a").replace(d, "n/a")}'
                f', Ver {value.get("Version", "n/a").replace(d, "n/a")}\n'
            )
        if value['dminame'] == 'BIOS Information':
            d = 'Default string'
            output += (
                f'* BIOS: {value.get("Vendor", "n/a").replace(d, "n/a")}'
                f', Ver {value.get("Version", "n/a").replace(d, "n/a")}'
                f' (released {value.get("Release Date", "n/a").replace(d, "n/a")})'
                f', ROM {value.get("ROM Size", "n/a").replace(d, "n/a")}\n'
            )
        if value['dminame'] == 'Chassis Information':
            d = 'Default string'
            output += (
                f'* Chassis:'
                f' {value.get("Manufacturer", "n/a").replace(d, "n/a")}'
                f', Type {value.get("Type", "n/a").replace(d, "n/a")}'
                f', SKU {value.get("SKU", "n/a").replace(d, "n/a")}'
                f', SerNo {value.get("Serial Number", "n/a").replace(d, "n/a")}\n'
            )
            output += (
                f'  States:'
                f' boot-up={value.get("Boot-up State", "n/a").replace(d, "n/a")}'
                f', pwr-supply={value.get("Power Supply State", "n/a").replace(d, "n/a")}'
                f', thermal={value.get("Thermal State", "n/a").replace(d, "n/a")}'
                f', security={value.get("Security Status", "n/a").replace(d, "n/a")}\n'
            )
        if value['dminame'] == 'Processor Information':
            d = 'Default string'
            output += (
                f'* Proc: {value.get("Manufacturer", "n/a").replace(d, "n/a")}'
                f', Ver {value.get("Version", "n/a").replace(d, "n/a")},\n'
            )
            output += (
                f'  Speed {value.get("Current Speed", "n/a").replace(d, "n/a")}'
                f'/{value.get("Max Speed", "n/a").replace(d, "n/a")} max.'
                f', {value.get("Core Enabled", "n/a").replace(d, "n/a")}'
                f'/{value.get("Core Count", "n/a").replace(d, "n/a")} Cores enabled'
                f', {value.get("Thread Count", "n/a").replace(d, "n/a")}'
                f' {lib.txt.pluralize("Thread", value.get("Thread Count", 0))}'
                f', Voltage {value.get("Voltage", "n/a").replace(d, "n/a")}\n'
            )
        if value['dminame'] == 'System Boot Information':
            d = 'Default string'
            output += f'* System Boot: {value.get("Status", "n/a").replace(d, "n/a")}\n'
        if value['dminame'] == 'System Information':
            d = 'Default string'
            output += (
                f'* SysInfo:'
                f' {value.get("Manufacturer", "n/a").replace(d, "n/a")}'
                f' {value.get("Product Name", "n/a").replace(d, "n/a")}'
                f', SerNo {value.get("Serial Number", "n/a").replace(d, "n/a")}'
                f', SKU {value.get("SKU", "n/a").replace(d, "n/a")}'
                f', Wake-up Type "{value.get("Wake-up Type", "n/a").replace(d, "n/a")}",\n'
            )
            output += (
                f'  UUID {value.get("UUID", "n/a").replace("Default string", "n/a")}\n'
            )
    if output:
        return f'Hardware Info (`dmidecode`):\n{output}\n'
    return output


def get_interfaces():
    output = ''
    try:
        for name, interface in sorted(psutil.net_if_addrs().items()):
            if name == 'lo':
                continue
            for addr in interface:
                if addr.family == lib.net.AF_INET:
                    output += (
                        f'* {name} {addr.address}/{lib.net.ip_to_cidr(addr.netmask)}\n'
                    )
    except Exception:
        pass
    if output:
        return f'Interfaces (IPv4):\n{output}\n'
    return output


def get_lftags(distro):
    """Detect Linuxfabrik tags for Icinga Director that apply to this host.

    The canonical tag catalog lives in
    `assets/icingaweb2-module-director/all-the-rest.json` under
    `DataList.tag_list.entries`. Every entry has:

      - `entry_name`:  lowercase slug used in Icinga `host.vars.tags`
                       (e.g. `strongswan`)
      - `entry_value`: human-readable display name (e.g. `strongSwan IPSec`)

    The `sw` field of each lftag below MUST equal the corresponding
    `entry_value` 1:1 so that `about-me --tags` output matches the Director
    tag picker. This sync is maintained manually. Tags that exist in the JSON
    but can't be detected from the host running this plugin (Windows-only
    software, remote-monitored appliances like Huawei Dorado or KEMP,
    SaaS endpoints) stay as commented-out placeholders.

    Detection per entry, any truthy signal triggers the tag:

      - `package`: list of distro package names. A tag fires when at least
        one of them is in the admin-installed set (via `dnf repoquery
        --userinstalled`, `apt-mark showmanual`, `pacman --query --explicit`,
        ...). Admin-intent semantics avoids false positives from transitive
        dependencies.
      - `expr`:    list of expressions evaluated at function call time
        (truthy/falsy). Typical: `run_cmd(['systemctl', 'is-enabled', '<unit>'],
        ignore_output=True)` or `os.path.isfile(...)` /  `os.path.isdir(...)`.

    If you add software that no package manager tracks and you only spot
    it via a directory or binary, mirror it in `get_software_found()` so
    it still surfaces in the operator-facing summary.
    """
    # cache values referenced by lftag expressions below
    db_flavor = lib.db_mysql.get_flavor()
    os_family = distro['os_family']
    lftags = [
        # template: {'sw': '<entry_value from all-the-rest.json tag_list>',
        #            'package': ['<pkg1>', '<pkg2>'],   # admin-installed → tag fires
        #            'expr':    [<stmnt1>, <stmnt2>]},  # any truthy → tag fires
        {
            'sw': 'acme.sh',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'acme-sh.timer'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'AIDE',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'aide-check.timer'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Apache httpd',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'httpd.service'], ignore_output=True
                ),
                run_cmd(
                    ['systemctl', 'is-enabled', 'apache2.service'], ignore_output=True
                ),
            ],
        },
        {
            'sw': 'Apache Solr',
            'package': [],
            'expr': [
                run_cmd(['systemctl', 'is-enabled', 'solr.service'], ignore_output=True)
            ],
        },
        {
            'sw': 'BIND',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'named.service'], ignore_output=True
                )
            ],
        },
        {'sw': 'Bonding', 'package': [], 'expr': [os.path.isdir('/proc/net/bonding')]},
        {
            'sw': 'BorgBackup',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'borg-backup-daily.timer'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Chronyd',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'chronyd.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'ClamAV',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'clamd@scan.service'],
                    ignore_output=True,
                )
            ],
        },
        {
            'sw': 'Collabora Online',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'coolwsd.service'], ignore_output=True
                )
            ],
        },
        {'sw': 'Composer', 'package': ['composer'], 'expr': []},
        {
            'sw': 'coturn',
            'package': ['coturn'],
            'expr': [run_cmd(['systemctl', 'is-enabled', 'coturn.service'])],
        },
        # {'sw': 'DHCP Client', 'package': [], 'expr': []},
        {
            'sw': 'Docker',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'docker.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Duplicity',
            'package': [],
            'expr': [
                run_cmd(['systemctl', 'is-enabled', 'duba.timer'], ignore_output=True)
            ],
        },
        {
            'sw': 'Elasticsearch',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'elasticsearch.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Exim4',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'exim4.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Fail2Ban',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'fail2ban.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'FirewallD',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'firewalld.service'], ignore_output=True
                )
            ],
        },
        # {'sw': 'FortiOS 6', 'package': [], 'expr': []},
        {
            'sw': 'FreeIPA Server',
            'package': [],
            'expr': [
                run_cmd(['systemctl', 'is-enabled', 'ipa.service'], ignore_output=True)
            ],
        },
        {
            'sw': 'Fwbuilder',
            'package': [],
            'expr': [
                run_cmd(['systemctl', 'is-enabled', 'fwb.service'], ignore_output=True)
            ],
        },
        {
            'sw': 'GitLab',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'gitlab-runsvdir.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Gluster Host',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'glusterd.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Grafana',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'grafana-server.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Grav',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'grav-selfupgrade.timer'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Graylog Server',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'graylog-server.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'H-Net eFaktura',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'hnet-securesvc.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'HAProxy',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'haproxy.service'], ignore_output=True
                )
            ],
        },
        # {'sw': 'Huawei Dorado', 'package': [], 'expr': []},
        {
            'sw': 'Icinga2',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'icinga2.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'IcingaDB',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'icingadb.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'InfluxDB',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'influxdb.service'], ignore_output=True
                )
            ],
        },
        # {'sw': 'Infomaniak Swiss Backup', 'package': [], 'expr': []},
        {'sw': 'IPMI', 'package': ['ipmitool'], 'expr': []},
        {
            'sw': 'iSCSI',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'iscsi.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Jitsi',
            'package': ['jitsi-videobridge2'],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'jitsi-videobridge2.service'],
                    ignore_output=True,
                )
            ],
        },
        {
            'sw': 'JumpCloud Agent',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'jcagent.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Keycloak',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'keycloak.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'KVM Host',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'libvirtd.service'], ignore_output=True
                )
            ],
        },
        {'sw': 'LibreNMS', 'package': [], 'expr': [os.path.isdir('/opt/librenms')]},
        {
            'sw': 'Logstash Client',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'filebeat.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Logstash Server',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'logstash.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'MariaDB',
            'package': [],
            'expr': [db_flavor == 'mariadb'],
        },
        {
            'sw': 'MariaDB Dump',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'mariadb-dump.timer'],
                    ignore_output=True,
                )
            ],
        },
        # MariaDB-* variants below are facets of the same server; we cannot
        # tell from the host which service sets the admin wants applied, so
        # we emit them all whenever MariaDB is detected
        {'sw': 'MariaDB InnoDB', 'package': [], 'expr': [db_flavor == 'mariadb']},
        {'sw': 'MariaDB Metrics', 'package': [], 'expr': [db_flavor == 'mariadb']},
        {'sw': 'MariaDB Replication', 'package': [], 'expr': [db_flavor == 'mariadb']},
        {'sw': 'MariaDB Schemas', 'package': [], 'expr': [db_flavor == 'mariadb']},
        {'sw': 'MariaDB Security', 'package': [], 'expr': [db_flavor == 'mariadb']},
        {
            'sw': 'Mastodon',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'mastodon-sidekiq.service'], ignore_output=True
                ),
                run_cmd(
                    ['systemctl', 'is-enabled', 'mastodon-streaming.service'],
                    ignore_output=True,
                ),
                run_cmd(
                    ['systemctl', 'is-enabled', 'mastodon-web.service'], ignore_output=True
                ),
            ],
        },
        {
            'sw': 'Matomo',
            'package': [],
            'expr': [
                os.path.isdir('/var/www/matomo'),
                os.path.isdir('/var/www/html/matomo'),
                os.path.isdir('/var/www/html/piwik'),
            ],
        },
        {
            'sw': 'MediaWiki',
            'package': [],
            'expr': [
                os.path.isdir('/var/www/html/mediawiki'),
                os.path.isdir('/var/www/html/wiki'),
                os.path.isdir('/var/www/mediawiki'),
                os.path.isdir('/var/www/wiki'),
            ],
        },
        {
            'sw': 'mod_qos',
            'package': ['mod_qos'],
            # httpd writes DUMP_MODULES to stderr, so cmd_output_contains checks both streams
            'expr': [
                os.path.isfile('/usr/lib64/httpd/modules/mod_qos.so'),
                os.path.isdir('/var/lib/mod_security'),
                cmd_output_contains(['httpd', '-t', '-D', 'DUMP_MODULES'], 'mod_qos'),
                cmd_output_contains(['apache2', '-t', '-D', 'DUMP_MODULES'], 'mod_qos'),
            ],
        },
        {
            'sw': 'MongoDB',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'mongod.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Moodle',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'moodle-cron.timer'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'mydumper',
            'package': ['mydumper'],
            'expr': [
                os.path.isfile('/etc/mydumper.cnf'),
                os.path.isfile('/usr/bin/mydumper'),
            ],
        },
        {
            'sw': 'MySQL',
            'package': [],
            'expr': [db_flavor == 'mysql'],
        },
        # MySQL-* variants below: see note above on MariaDB-* variants
        {'sw': 'MySQL InnoDB', 'package': [], 'expr': [db_flavor == 'mysql']},
        {'sw': 'MySQL Metrics', 'package': [], 'expr': [db_flavor == 'mysql']},
        {'sw': 'MySQL Replication', 'package': [], 'expr': [db_flavor == 'mysql']},
        {'sw': 'MySQL Schemas', 'package': [], 'expr': [db_flavor == 'mysql']},
        {'sw': 'MySQL Security', 'package': [], 'expr': [db_flavor == 'mysql']},
        {
            'sw': 'Needs Restarting',
            'package': ['dnf-utils', 'yum-utils'],
            'expr': [os.path.isfile('/usr/bin/needs-restarting')],
        },
        {
            'sw': 'networking.service',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'networking.service'],
                    ignore_output=True,
                )
            ],
        },
        {
            'sw': 'Nextcloud',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'nextcloud-jobs.timer'],
                    ignore_output=True,
                )
            ],
        },
        {
            'sw': 'NFS Server',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'nfs-server.service'],
                    ignore_output=True,
                )
            ],
        },
        {
            'sw': 'Nginx',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'nginx.service'], ignore_output=True
                )
            ],
        },
        {'sw': 'NodeBB', 'package': [], 'expr': [os.path.isdir('/opt/nodebb')]},
        {
            'sw': 'NTPd',
            'package': [],
            'expr': [
                run_cmd(['systemctl', 'is-enabled', 'ntpd.service'], ignore_output=True)
            ],
        },
        {
            'sw': 'OnlyOffice',
            'package': [],
            'expr': [os.path.isdir('/var/log/onlyoffice')],
        },
        {
            'sw': 'OpenVAS',
            'package': [],
            'expr': [
                run_cmd(['systemctl', 'is-enabled', 'gsad.service'], ignore_output=True)
            ],
        },
        {
            'sw': 'OpenVPN Server',
            'package': ['openvpn'],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'openvpn-server@server.service'],
                    ignore_output=True,
                )
            ],
        },
        {
            'sw': f'OS - {distro.get("os_info")}, family "{os_family}"',
            'package': [],
            'expr': [True],  # always check this
        },
        {'sw': 'PHP', 'package': ['php'], 'expr': [os.path.isfile('/etc/php.ini')]},
        {
            'sw': 'PHP-FPM',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'php-fpm.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'pip',
            'package': ['pip3', 'pip2', 'python3-pip', 'python2-pip'],
            'expr': [],
        },
        {
            'sw': 'Podman',
            'package': ['podman'],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'podman.socket'], ignore_output=True
                ),
                run_cmd(
                    ['systemctl', 'is-enabled', 'podman-auto-update.timer'], ignore_output=True
                ),
                run_cmd(
                    ['systemctl', 'is-enabled', 'podman-restart.service'], ignore_output=True
                ),
            ],
        },
        {
            'sw': 'Postfix MTA',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'postfix.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'PostgreSQL',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'postgresql.service'],
                    ignore_output=True,
                )
            ],
        },
        {'sw': 'Proxmox', 'package': [], 'expr': [os.path.isdir('/etc/pve')]},
        {'sw': 'Python', 'package': ['python3', 'python2'], 'expr': []},
        # {'sw': 'QNAP QTS', 'package': [], 'expr': []},
        {
            'sw': 'RabbitMQ Server',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'rabbitmq-server.service'], ignore_output=True
                )
            ],
        },
        # {'sw': 'Redfish', 'package': [], 'expr': []},
        {
            'sw': 'Redis',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'redis.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'restic',
            'package': ['restic'],
            'expr': [run_cmd(['restic', 'version'], ignore_output=True)],
        },
        {
            'sw': 'Rocket.Chat',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'rocketchat.service'],
                    ignore_output=True,
                )
            ],
        },
        # {'sw': 'RPM Updates', 'package': [], 'expr': []},
        {
            'sw': 'rsyncd',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'rsyncd.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'rsyslog',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'rsyslog.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Samba',
            'package': [],
            'expr': [
                run_cmd(['systemctl', 'is-enabled', 'smb.service'], ignore_output=True)
            ],
        },
        {'sw': 'Scanrootkit', 'package': [], 'expr': [True]},
        {
            'sw': 'snmpd',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'snmpd.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Splunk',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'splunk.service'], ignore_output=True
                )
            ],
        },
        # {'sw': 'Starface PBX', 'package': [], 'expr': []},
        {
            'sw': 'strongSwan IPSec',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'strongswan.service'],
                    ignore_output=True,
                )
            ],
        },
        {
            'sw': 'syslog-ng',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'syslog-ng.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'System Update',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'notify-and-schedule.timer'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'Systemd Timesyncd',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'systemd-timesyncd.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'TuneD',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'tuned.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'UPS (Network UPS Tools, nut)',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'nut-server.service'],
                    ignore_output=True,
                )
            ],
        },
        {
            'sw': 'Valkey',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'valkey.service'], ignore_output=True
                )
            ],
        },
        {
            'sw': 'vsftpd',
            'package': [],
            'expr': [
                run_cmd(
                    ['systemctl', 'is-enabled', 'vsftpd.service'], ignore_output=True
                )
            ],
        },
        {'sw': 'Wildfly', 'package': [], 'expr': [os.path.isdir('/opt/wildfly')]},
        {
            'sw': 'Wordpress',
            'package': [],
            'expr': [
                os.path.isdir('/var/www/html/wordpress'),
                os.path.isdir('/var/www/html/wp-config.php'),
                os.path.isdir('/var/www/wordpress'),
            ],
        },
        # {'sw': 'XFS', 'package': [], 'expr': []},
    ]
    # determine explicitly installed packages (admin intent), distro-agnostically.
    # rules out transitive deps to avoid false-positive tags like IPMI on a
    # workstation that only has `ipmitool` as an indirect dependency.
    if os_family == 'RedHat':
        pkg_query = [
            'dnf',
            'repoquery',
            '--userinstalled',
            '--queryformat',
            r'%{name}\n',
        ]
    elif os_family == 'Debian':
        pkg_query = ['apt-mark', 'showmanual']
    elif os_family == 'Archlinux':
        pkg_query = ['pacman', '--query', '--explicit', '--quiet']
    elif os_family == 'Alpine':
        # apk has no admin-intent query; alpine's lean dep model makes the
        # presence/intent distinction less noisy in practice
        pkg_query = ['apk', 'info']
    elif os_family == 'Suse':
        # zypper/rpm offer no clean admin-intent equivalent; accept the
        # broader presence semantic and the resulting false-positive risk
        pkg_query = ['rpm', '--query', '--all', '--queryformat', r'%{NAME}\n']
    else:
        pkg_query = []
    installed_packages = set()
    if pkg_query:
        success, result = lib.shell.shell_exec(pkg_query)
        if success:
            stdout, _, _ = result
            installed_packages = set(stdout.splitlines())

    output = ''
    for item in lftags:
        # if any of the listed software packages is installed, we have a match
        if any(p in installed_packages for p in item['package']) or any(item['expr']):
            output += f'* {item["sw"]}\n'
    if output:
        return f"Linuxfabrik's Icinga Director Tags:\n{output}\n"
    return ''


def get_listening_ports():
    ports = {}
    output = []
    try:
        nc = psutil.net_connections('inet')
        for c in nc:
            if c.status not in (psutil.CONN_LISTEN, psutil.CONN_NONE):
                continue
            if c.type == socket.SOCK_STREAM:
                proto = 'tcp'
            elif c.type == socket.SOCK_DGRAM:
                proto = 'udp'
            else:
                continue
            if c.family == socket.AF_INET:
                proto += '4'
            else:
                proto += '6'
            ip, port = c.laddr
            ports[f'{ip}#{port}#{proto}'] = {
                'proto': proto,
                'ip': f'[{ip}]'
                if c.family == socket.AF_INET6
                and not ip.startswith('[')
                and not ip.endswith(']')
                else ip,
                'port': port,
            }
        for _, value in ports.items():
            output.append(value)
        output = sorted(output, key=lambda d: (d['port'], d['proto'], d['ip']))
    except Exception:
        pass
    if output:
        msg = 'Listening TCP/UDP Ports (ordered by port, proto, ip):\n'
        for p in output:
            msg += f'* {p["ip"]}:{p["port"]}/{p["proto"]}\n'
        msg += '\n'
        return msg
    return ''


def get_userinstalled_software():
    success, result = lib.shell.shell_exec(
        [
            'dnf',
            'repoquery',
            '--userinstalled',
            '--queryformat',
            r'%{name};%{version};%{from_repo};%{installtime}\n',
        ]
    )
    if not success:
        return ''
    stdout, _, _ = result
    table_data = []
    header = ['name', 'version', 'from_repo', 'installtime']
    for line in stdout.splitlines():
        # dnf 4 appends an extra newline per record on top of the \n in
        # --queryformat, so splitlines() yields blanks between records
        if not line.strip():
            continue
        data = dict(zip(header, line.split(';')))
        table_data.append(data)
    table_data.sort(key=lambda r: _natural_key(r['name']))
    output = lib.base.get_table(
        table_data,
        header,
        header=header,
    )
    if output:
        return f'User-Installed Software (ordered by name):\n{output}\n'
    return ''


def get_nondefault_users():
    default_linux_users = [
        '_apt',
        '_rpc',
        'abrt',
        'adm',
        'avahi',
        'backup',
        'bin',
        'chrony',
        'colord',
        'daemon',
        'dbus',
        'ftp',
        'games',
        'gnats',
        'halt',
        'irc',
        'list',
        'lp',
        'mail',
        'man',
        'messagebus',
        'news',
        'nobody',
        'operator',
        'pi',
        'polkitd',
        'proxy',
        'pulse',
        'rngd',
        'root',
        'rpc',
        'rpcuser',
        'shutdown',
        'sshd',
        'sssd',
        'sync',
        'sys',
        'systemd-coredump',
        'systemd-network',
        'systemd-oom',
        'systemd-resolve',
        'systemd-timesync',
        'tcpdump',
        'tss',
        'unbound',
        'user',
        'uucp',
    ]
    passwd = lib.base.coe(lib.disk.read_file('/etc/passwd'))
    table_data = []
    header = ['user', 'pw', 'uid', 'gid', 'comment', 'home_dir', 'user_shell']
    for line in passwd.splitlines():
        data = dict(zip(header, line.split(':')))
        if data['user'] not in default_linux_users:
            table_data.append(data)

    table_data.sort(key=lambda r: _natural_key(r['user']))
    output = lib.base.get_table(
        table_data,
        header,
        header=header,
    )
    if output:
        return f'Non-default Users:\n{output}\n'
    return ''


def get_public_ip(args):
    success, pub_ip = lib.net.get_public_ip(
        args.PUBLIC_IP_URL,
        insecure=args.INSECURE,
        no_proxy=args.NO_PROXY,
        timeout=args.TIMEOUT,
    )
    if success:
        return f'Public IP {pub_ip}, '
    return ''


def get_software_found():
    """Manually installed software, found on the system"""
    guessed = [
        {
            'sw': 'Apache Solr',
            'expr': [os.path.isdir('/opt/apache-solr'), os.path.isdir('/opt/solr')],
        },
        {
            'sw': 'Apache Tomcat',
            'expr': [os.path.isdir('/opt/apache-tomcat'), os.path.isdir('/opt/tomcat')],
        },
        {
            'sw': 'Atlassian Bitbucket',
            'expr': [os.path.isdir('/opt/atlassian/bitbucket')],
        },
        {
            'sw': 'Atlassian Confluence',
            'expr': [os.path.isdir('/opt/atlassian/confluence')],
        },
        {'sw': 'Atlassian Jira', 'expr': [os.path.isdir('/opt/atlassian/jira')]},
        {'sw': 'Atomicorp', 'expr': [os.path.isdir('/opt/atomicorp')]},
        {'sw': 'Bacchus', 'expr': [os.path.isdir('/opt/bacchus')]},
        {'sw': 'Contao', 'expr': [os.path.isdir('/var/www/html/contao')]},
        {'sw': 'DCM4CHEE', 'expr': [os.path.isdir('/opt/dcm4chee')]},
        {'sw': 'Django', 'expr': [os.path.isdir('/opt/django')]},
        {
            'sw': 'Grav',
            'expr': [
                os.path.isdir('/var/www/html/grav'),
                os.path.isdir('/var/www/grav'),
            ],
        },
        {
            'sw': 'H-Net eFaktura',
            'expr': [os.path.isdir('/home/hnet/HnetSecureService')],
        },
        {'sw': 'Hostbill', 'expr': [os.path.isdir('/home/hostbill')]},
        {'sw': 'HTMLy', 'expr': [os.path.isdir('/var/www/html/htmly')]},
        {'sw': 'JBoss', 'expr': [os.path.isdir('/opt/jboss')]},
        {'sw': 'JumpCloud Agent', 'expr': [os.path.isdir('/opt/jc')]},
        {'sw': 'KeeWeb', 'expr': [os.path.isdir('/opt/KeeWeb')]},
        {
            'sw': 'Keycloak',
            'expr': [
                os.path.isdir('/opt/keycloak'),
                os.path.isdir('/var/log/keycloak'),
            ],
        },
        {'sw': 'LibreNMS', 'expr': [os.path.isdir('/opt/librenms')]},
        {
            'sw': 'MariaDB ColumnStore',
            'expr': [os.path.isdir('/usr/local/mariadb/columnstore')],
        },
        {
            'sw': 'Matomo',
            'expr': [
                os.path.isdir('/var/www/matomo'),
                os.path.isdir('/var/www/html/matomo'),
                os.path.isdir('/var/www/html/piwik'),
            ],
        },
        {
            'sw': 'MediaWiki',
            'expr': [
                os.path.isdir('/var/www/mediawiki'),
                os.path.isdir('/var/www/html/mediawiki'),
            ],
        },
        {'sw': 'Medidata (eFaktura)', 'expr': [os.path.isdir('/opt/MPCommunicator')]},
        {'sw': 'Metabase', 'expr': [os.path.isdir('/opt/metabase')]},
        {
            'sw': 'Nextcloud',
            'expr': [
                os.path.isdir('/var/www/html/nextcloud'),
                os.path.isdir('/var/www/nextcloud'),
                os.path.isfile('/var/www/html/nextcloud/occ'),
            ],
        },
        {'sw': 'NodeBB', 'expr': [os.path.isdir('/opt/nodebb')]},
        {'sw': 'OnlyOffice', 'expr': [os.path.isdir('/var/log/onlyoffice')]},
        {
            'sw': 'ownCloud',
            'expr': [
                os.path.isdir('/var/www/owncloud'),
                os.path.isdir('/var/www/html/owncloud'),
            ],
        },
        {'sw': 'Rambox', 'expr': [os.path.isdir('/opt/Rambox')]},
        {
            'sw': 'Rocket.Chat',
            'expr': [
                os.path.isdir('/opt/Rocket.Chat'),
                os.path.isdir('/opt/rocket.chat'),
            ],
        },
        {'sw': 'Roundcube', 'expr': [os.path.isdir('/var/www/html/roundcubemail')]},
        {'sw': 'Tarifpool v2', 'expr': [os.path.isdir('/opt/tarifpool')]},
        {'sw': 'VMware Tools', 'expr': [os.path.isdir('/etc/vmware-tools')]},
        {'sw': 'Vtiger', 'expr': [os.path.isdir('/var/www/html/vtigercrm')]},
        {'sw': 'WHMCS', 'expr': [os.path.isdir('/home/whmcs')]},
        {'sw': 'Wildfly', 'expr': [os.path.isdir('/opt/wildfly')]},
        {
            'sw': 'Wordpress',
            'expr': [
                os.path.isdir('/var/www/html/wordpress'),
                os.path.isdir('/var/www/html/wp-config.php'),
                os.path.isdir('/var/www/wordpress'),
            ],
        },
        {
            'sw': 'Yii2',
            'expr': [
                os.path.isdir('/var/www/html/yii2'),
                os.path.isdir('/var/www/html/yii2-advanced'),
                os.path.isdir('/var/www/html/yii2-basic'),
                os.path.isdir('/var/www/html/yii'),
            ],
        },
        {'sw': 'Zimbra', 'expr': [os.path.isdir('/opt/zimbra')]},
    ]
    output = ''
    for item in guessed:
        # if any of the listed software packages is installed, we have a match
        if any(item['expr']):
            output += f'* {item["sw"]}\n'
    if output:
        return f'Software found elsewhere (just guessed):\n{output}\n'
    return ''


def get_sys_dimensions():
    """get some very basic system statistics"""
    sys_dimensions = {}
    try:
        sys_dimensions['cpu_logical'] = psutil.cpu_count(logical=True)
        sys_dimensions['cpu_physical'] = psutil.cpu_count(logical=False)
        sys_dimensions['cpu_usable'] = psutil.cpu_count(logical=True)
        sys_dimensions['cpu_freq'] = psutil.cpu_freq()
        sys_dimensions['ram'] = psutil.virtual_memory().total
    except Exception:
        pass
    return sys_dimensions


def get_systemd_default_target():
    cmd = ['systemctl', 'get-default']
    success, result = lib.shell.shell_exec(cmd)
    if not success:
        return ''
    stdout, stderr, retc = result
    if stderr or retc != 0:
        return ''
    return f'systemctl get-default:\n* {stdout.strip()}\n\n'


def get_systemd_timers():
    # using `--output=json` sadly does not work with older systemd versions
    # (eg systemd 219 on CentOS 7 or systemd 239 on RHEL 8), therefore we have to parse
    # the human output.
    # in order to list for a different user (`--user`), we would need to sudo to that user
    # first - we will skip that for now
    success, result = lib.shell.shell_exec(['systemctl', 'list-timers'])
    if not success:
        return ''
    stdout, stderr, retc = result
    if stderr or retc != 0:
        return ''

    table_data = []
    next_pos = None
    left_pos = None
    unit_pos = None
    activates_pos = None
    for line in stdout.splitlines():
        if next_pos is None:
            # clutters a little bit on modern systemd (Fedora) because of right-aligned LEFT
            next_pos = line.find('NEXT')
            left_pos = line.find('LEFT')
            unit_pos = line.find('UNIT')
            activates_pos = line.find('ACTIVATES')
        if '.timer' in line:
            table_data.append(
                {
                    'unit': line[unit_pos:activates_pos].strip(),
                    'activates': line[activates_pos:].strip(),
                    'next': line[next_pos:left_pos].strip(),
                }
            )

    table_data.sort(key=lambda r: _natural_key(r['unit']))
    output = lib.base.get_table(
        table_data,
        ['unit', 'activates', 'next'],
        header=['unit', 'activates', 'next'],
    )
    if output:
        return f'systemctl list-timers:\n{output}\n'
    return ''


def get_systemd_units(cmd):
    # using `--output=json` sadly does not work with older systemd versions
    # (eg systemd 219 on CentOS 7 or systemd 239 on RHEL 8), therefore we have to parse
    # the human output.
    # in order to list for a different user (`--user`), we would need to sudo to that user
    # first - we will skip that for now
    success, result = lib.shell.shell_exec(cmd)
    if not success:
        return ''
    stdout, stderr, retc = result
    if stderr or retc != 0:
        return ''
    if not stdout:
        return ''

    output = f'{" ".join(arg for arg in cmd if arg != "--no-legend")}:\n'
    for line in stdout.splitlines():
        output += f'* {line.split()[0]}\n'
    return output + '\n'


def get_tuned_active_profile():
    """Return current active tuned profile (if any)."""
    output = run_cmd(['tuned-adm', 'active'])
    if output:
        return (
            f'tuned profile "{run_regex(output, ": (.*)").strip().replace("* ", "")}", '
        )
    return ''


def get_virt_info():
    # alternative would be `/usr/sbin/virt-what` (POSIX shell script)
    success, result = lib.shell.shell_exec(['systemd-detect-virt'])
    if success:
        stdout, _, _ = result
        return stdout.strip()
    return 'Unknown'


def cmd_output_contains(cmd, needle):
    """Run cmd (argv list) and return True if needle appears in its stdout/stderr."""
    env = os.environ.copy()
    env['LC_ALL'] = 'C'
    env['PATH'] += ':/usr/local/bin:/usr/local/sbin'
    success, result = lib.shell.shell_exec(cmd, env=env)
    if not success:
        return False
    stdout, stderr, _retc = result
    return needle in stdout or needle in stderr


def run_cmd(cmd, ignore_output=False):
    """Run a command and return its output. Returns stderr if cmd prints its standard output there.
    If ignore_output is set to True, returns True. Returns False if cmd is not found.
    """
    env = os.environ.copy()
    env['LC_ALL'] = 'C'
    env['PATH'] += ':/usr/local/bin:/usr/local/sbin'
    success, result = lib.shell.shell_exec(cmd, env=env)
    if not success:
        return False
    stdout, stderr, retc = result
    if retc != 0:
        # for example if using `command -v loolwsd`
        return False
    if ignore_output:
        return True
    if stdout == '' and stderr != '':
        # https://stackoverflow.com/questions/26028416/why-does-python-print-version-info-to-stderr
        # https://stackoverflow.com/questions/13483443/why-does-java-version-go-to-stderr
        stdout = stderr
    try:
        return stdout.strip().splitlines()[0].strip()
    except IndexError:
        return True


def run_regex(haystack, regex, group=1):
    """Apply a regex to a haystack, assume first match group, otherwise let us choose which one."""
    re_search = re.search(regex, haystack)
    if re_search:
        return re_search.group(group).strip()
    return ''


def main():
    """The main function. This is where the magic happens."""

    # parse the command line
    try:
        args = parse_args()
    except SystemExit:
        sys.exit(STATE_UNKNOWN)

    # init some vars
    distro = lib.distro.get_distribution_facts()
    perfdata = ''
    tags = get_lftags(distro)

    # only tags wanted, so it's ok to stop here
    if args.TAGS:
        lib.base.oao(tags, STATE_OK)

    # build the message

    # first header line ----------------------------------------------------------------------------
    msg = f'{socket.gethostname()}: '

    os_info = distro.get('os_info')
    if os_info:
        msg += f'{os_info} '
    msg += f'Kernel {run_cmd(["uname", "-r"])} '

    virt = get_virt_info()
    if virt == 'none':
        msg += 'on Bare-Metal, '
    else:
        msg += f'virtualized on {virt}, '

    success, firmware_device_model = lib.disk.read_file(
        '/sys/firmware/devicetree/base/model'
    )
    if not success:
        firmware_device_model = ''
    if firmware_device_model:
        msg += f'{firmware_device_model}, '

    dmi = lib.dmidecode.get_data() if args.DMIDECODE else False
    sys_dimensions = get_sys_dimensions()
    if dmi and sys_dimensions:
        # combine the best from both worlds
        msg += f'{lib.dmidecode.manufacturer(dmi)} {lib.dmidecode.model(dmi)}, '
        msg += f'Firmware: {lib.dmidecode.firmware(dmi)}, '
        msg += f'SerNo: {lib.dmidecode.serno(dmi)}, '
        msg += f'Proc: {lib.dmidecode.cpu_type(dmi)}, '
        msg += (
            f'{sys_dimensions["cpu_physical"]}'
            f'/{sys_dimensions["cpu_logical"]}'
            f'/{sys_dimensions["cpu_usable"]}'
            f' CPUs (phys/lcpu/onln), '
        )
        if sys_dimensions['cpu_freq']:
            msg += f'Current Speed: {int(sys_dimensions["cpu_freq"][0])} MHz, '
        msg += (
            f'{lib.human.bytes2human(sys_dimensions["ram"])}'
            f'/{lib.human.bytes2human(lib.dmidecode.ram(dmi))}'
            f' RAM (virtmem/max'
        )
        if sys_dimensions['ram'] > lib.dmidecode.ram(dmi):
            msg += '; reboot recommended'
        msg += '), '
    elif dmi:
        msg += f'{lib.dmidecode.manufacturer(dmi)} {lib.dmidecode.model(dmi)}, '
        msg += f'Firmware: {lib.dmidecode.firmware(dmi)}, '
        msg += f'SerNo: {lib.dmidecode.serno(dmi)}, '
        msg += f'Proc: {lib.dmidecode.cpu_type(dmi)}, '
        msg += f'{lib.human.bytes2human(lib.dmidecode.ram(dmi))} RAM, '
    elif sys_dimensions:
        msg += (
            f'{sys_dimensions["cpu_physical"]}'
            f'/{sys_dimensions["cpu_logical"]}'
            f'/{sys_dimensions["cpu_usable"]}'
            f' CPUs (phys/lcpu/onln), '
        )
        if sys_dimensions['cpu_freq']:
            msg += f'Current Speed: {int(sys_dimensions["cpu_freq"][0])} MHz, '
        msg += f'{lib.human.bytes2human(sys_dimensions["ram"])} RAM, '
    else:
        msg += 'sys dimensions n/a (consider installing dmidecode/psutil), '

    disk_msg, disk_count = get_disks()
    msg += disk_msg
    msg += get_boot_mode()
    env = os.environ.copy()
    display_server = env.get('XDG_SESSION_TYPE')
    if display_server and display_server != 'unspecified':
        msg += f'Display Server {display_server}, '
    msg += get_tuned_active_profile()
    msg += get_public_ip(args)
    msg += get_birthday()
    msg += f'About-me v{__version__}\n\n'

    # multi-line content - print further lines -----------------------------------------------------
    if args.DMIDECODE:
        msg += get_hw_info()
    msg += get_interfaces()
    msg += get_listening_ports()
    msg += get_userinstalled_software()
    msg += get_software_found()
    msg += get_nondefault_users()
    msg += get_systemd_default_target()
    msg += get_systemd_units(
        [
            'systemctl',
            'list-unit-files',
            '--type=service',
            '--state=enabled',
            '--no-legend',
        ],
    )
    msg += get_systemd_units(
        [
            'systemctl',
            'list-unit-files',
            '--type=mount',
            '--state=static',
            '--state=generated',
            '--no-legend',
        ],
    )
    msg += get_systemd_units(
        [
            'systemctl',
            'list-unit-files',
            '--type=automount',
            '--state=enabled',
            '--state=static',
            '--no-legend',
        ],
    )
    msg += get_systemd_timers()
    msg += get_crontab()
    msg += tags

    # perfdata
    if os_info:
        re_search = re.search(r'[\d\.]+', os_info)
        if re_search:
            perfdata += lib.base.get_perfdata(
                'osversion',
                re_search.group(0).replace('.', ''),
            )
    if sys_dimensions:
        perfdata += lib.base.get_perfdata(
            'cpu_logical',
            sys_dimensions['cpu_logical'],
            _min=0,
        )
        perfdata += lib.base.get_perfdata(
            'cpu_physical',
            sys_dimensions['cpu_physical'],
            _min=0,
        )
        perfdata += lib.base.get_perfdata(
            'cpu_usable',
            sys_dimensions['cpu_usable'],
            _min=0,
        )
        if sys_dimensions['cpu_freq']:
            perfdata += lib.base.get_perfdata(
                'cpu_freq',
                int(sys_dimensions['cpu_freq'][0]),
                _min=0,
                _max=lib.dmidecode.cpu_speed(dmi) if args.DMIDECODE and dmi else None,
            )
    if dmi:
        perfdata += lib.base.get_perfdata(
            'ram',
            lib.dmidecode.ram(dmi),
            uom='B',
            _min=0,
        )
    elif sys_dimensions:
        perfdata += lib.base.get_perfdata(
            'ram',
            sys_dimensions['ram'],
            uom='B',
            _min=0,
        )
    perfdata += lib.base.get_perfdata(
        'disks',
        disk_count,
        _min=0,
    )

    # over and out
    lib.base.oao(msg, STATE_OK, perfdata)


if __name__ == '__main__':
    try:
        main()
    except Exception:
        lib.base.cu()
