#!/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[*]}")"