Files

178 lines
7.9 KiB
Bash
Executable File

#!/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, Panama's
# own revision, firmware, Secure Boot, 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"
}
# Which Panama this is, from the checkout it is running out of. A version
# string restated in a file would be a second source of truth that goes stale
# the moment somebody forgets to bump it; the commit cannot. Absent outside a
# git checkout -- an installed copy without .git legitimately has no answer.
# The script's own directory, not a counted number of "..": this tree is
# symlinked into ~/.config/quickshell, and git discovers the checkout by
# walking up from wherever it is told to start.
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if command -v git >/dev/null 2>&1 && git -C "$repo_root" rev-parse --git-dir >/dev/null 2>&1; then
panama_rev="$(git -C "$repo_root" describe --tags --always --dirty 2>/dev/null)"
panama_age="$(git -C "$repo_root" log -1 --format=%cr 2>/dev/null)"
emit "Panama" "${panama_rev}${panama_age:+ · $panama_age}"
fi
vendor="$(dmi sys_vendor)"
product="$(dmi product_name)"
if [[ -n "$vendor" && -n "$product" ]]; then
emit "Model" "$vendor $product"
else
emit "Model" "${product:-$vendor}"
fi
# The BIOS/UEFI version, which is the one firmware fact a person is ever asked
# for. DMI first because it is a free file read; bootctl only as the fallback,
# since it reports "n/a" on this class of machine and shelling out for that
# would be a slower way to learn nothing.
firmware="$(dmi bios_version)"
if [[ -z "$firmware" ]] && command -v bootctl >/dev/null 2>&1; then
firmware="$(bootctl status 2>/dev/null \
| awk -F': *' '/^ *Firmware:/ { print $2; exit }' \
| sed 's/ *(n\/a)$//')"
[[ "$firmware" == "n/a"* ]] && firmware=""
fi
bios_date="$(dmi bios_date)"
emit "Firmware" "${firmware:+${firmware}${bios_date:+ · $bios_date}}"
# Absent-tolerant on purpose: mokutil is not installed everywhere, and a
# machine that cannot answer "is Secure Boot on" should not claim it is off.
if command -v mokutil >/dev/null 2>&1; then
case "$(mokutil --sb-state 2>/dev/null)" in
*"SecureBoot enabled"*) emit "Secure Boot" "Enabled" ;;
*"SecureBoot disabled"*) emit "Secure Boot" "Disabled" ;;
esac
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[*]}")"