178 lines
7.9 KiB
Bash
Executable File
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[*]}")"
|