#!/usr/bin/env bash # What this machine is, as JSON: {label, value} pairs in display order. # # GNOME's About panel answers "what am I running on" in one screen, and # fastfetch answers it in more detail; this covers both -- model, OS, kernel, # uptime, package counts, shell, resolution, processor, memory, swap, disk and # locale. Rows are ordered roughly the way fastfetch presents them: what the # system is, then what is installed on it, then the hardware underneath. # # Graphics is deliberately absent: GraphicsDevices already enumerates GPUs for # the vitals readout, and naming them again here would be a second source of # truth that could disagree with the first. The page joins the two. # # Anything unreadable is omitted rather than reported as "Unknown". These are # facts about hardware, and a row saying "Processor: Unknown" is noise where # simply not having the row is not. set -uo pipefail facts=() emit() { [[ -n "${2:-}" ]] || return 0 facts+=("$(jq -cn --arg label "$1" --arg value "$2" '{label: $label, value: $value}')") } # ── System ─────────────────────────────────────────────────────────────────── if [[ -r /etc/os-release ]]; then # Sourced in a subshell so the variables cannot leak into this script. os="$( . /etc/os-release 2>/dev/null && printf '%s' "${PRETTY_NAME:-$NAME}" )" emit "Operating system" "$os" fi # DMI strings are frequently placeholders ("To Be Filled By O.E.M.", "Default # string"). Those are worse than nothing, so they are filtered out. dmi() { local value value="$(cat "/sys/class/dmi/id/$1" 2>/dev/null)" || return 0 case "$value" in ""|"To Be Filled By O.E.M."*|"Default string"|"System Product Name"|"Unknown"|"None") return 0 ;; esac printf '%s' "$value" } vendor="$(dmi sys_vendor)" product="$(dmi product_name)" if [[ -n "$vendor" && -n "$product" ]]; then emit "Model" "$vendor $product" else emit "Model" "${product:-$vendor}" fi emit "Hostname" "$(hostnamectl hostname 2>/dev/null || hostname 2>/dev/null)" emit "Kernel" "$(uname -r 2>/dev/null)" # `uptime -p` already reads as prose ("1 week, 23 hours, 5 minutes"); only the # leading "up " needs removing. emit "Uptime" "$(uptime -p 2>/dev/null | sed 's/^up //')" # ── Installed ──────────────────────────────────────────────────────────────── # Counted rather than listed. rpm -qa on a full workstation is a few thousand # lines and takes a moment, which is part of why this whole helper runs on # demand rather than at startup. packages="" if command -v rpm >/dev/null 2>&1; then rpm_count="$(rpm -qa 2>/dev/null | wc -l)" [[ "$rpm_count" -gt 0 ]] && packages="$rpm_count rpm" fi if command -v flatpak >/dev/null 2>&1; then flatpak_count="$(flatpak list --app 2>/dev/null | wc -l)" if [[ "$flatpak_count" -gt 0 ]]; then [[ -n "$packages" ]] && packages="$packages, " packages="$packages$flatpak_count flatpak" fi fi emit "Packages" "$packages" # $SHELL is the login shell, which is the one worth reporting -- the shell this # script happens to run under is an implementation detail of the caller. if [[ -n "${SHELL:-}" ]]; then shell_name="$(basename "$SHELL")" shell_version="$("$SHELL" --version 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1)" emit "Shell" "${shell_name}${shell_version:+ $shell_version}" fi emit "Locale" "${LANG:-}" case "${XDG_SESSION_TYPE:-}" in wayland) emit "Windowing system" "Wayland" ;; x11) emit "Windowing system" "X11" ;; esac # ── Hardware ───────────────────────────────────────────────────────────────── # The focused monitor's actual mode, including the fractional scale, since a # 4500x3000 panel at 1.5 presents very differently from one at 1. if command -v hyprctl >/dev/null 2>&1; then emit "Resolution" "$(hyprctl -j monitors 2>/dev/null \ | jq -r 'map(select(.focused)) + . | .[0] | select(. != null) | "\(.width)x\(.height) @ \(.refreshRate | floor)Hz · scale \(.scale)"' 2>/dev/null)" fi cpu="$(awk -F': ' '/^model name/ { print $2; exit }' /proc/cpuinfo 2>/dev/null)" threads="$(nproc 2>/dev/null)" if [[ -n "$cpu" ]]; then # The marketing name usually already says "6-Core", so the thread count is # the part that adds information. [[ -n "$threads" ]] && cpu="$cpu ($threads threads)" emit "Processor" "$cpu" fi # Reported as the kernel sees it, which is a little under the sticker figure # because firmware and integrated graphics reserve some before Linux starts. # # Total rather than used: About is not a monitor, and a "12.4 GiB used" figure # is stale before it finishes drawing. The Home page's vitals readout is where # live numbers belong. emit "Memory" "$(awk '/^MemTotal:/ { printf "%.1f GiB", $2 / 1048576 }' /proc/meminfo 2>/dev/null)" swap="$(awk '/^SwapTotal:/ { if ($2 > 0) printf "%.1f GiB", $2 / 1048576 }' /proc/meminfo 2>/dev/null)" emit "Swap" "$swap" # Decimal units with explicit GB/TB labels, matching the Storage page and the # way drives are actually sold. `df -h` is binary but prints a bare "G", so the # same drive read 488G here and 523 GB there. read -r size avail <<<"$(df -B1 --output=size,avail / 2>/dev/null | tail -1)" if [[ -n "${size:-}" ]]; then emit "Disk" "$(awk -v a="$avail" -v s="$size" ' function human(v, units, i) { split("B KB MB GB TB PB", units, " ") i = 1 while (v >= 1000 && i < 6) { v /= 1000; i++ } return sprintf("%.*f %s", (v < 10 && i > 2) ? 1 : 0, v, units[i]) } BEGIN { printf "%s free of %s", human(a), human(s) }')" fi printf '[%s]\n' "$(IFS=,; printf '%s' "${facts[*]}")"